Avocado Studio is Apache 2.0 licensed and self-hostable: you run the whole stack on your own infrastructure, on your own Anthropic, OpenAI or Google API keys, with no per-seat licence. The orchestrator (the brain that runs sessions, calls the LLMs, and serves draft state) is the only stateful service in the stack, and Docker is the supported path for running it. If you need help self-hosting, get in touch — we would rather hear about the sharp edge than have you work around it.
Where to host the orchestrator
The orchestrator is a small Node.js Fastify service. It’s stateful but not heavy — most production deployments fit comfortably on the smallest paid tier of any modern container host.What the host has to give you
Any host works if it offers three things: one long-lived container, a persistent volume you can mount at/app/.data, and request timeouts you can raise. The list below is how that maps onto the usual candidates — it is a shape guide, not a compatibility matrix, and we have not run a deployment on every row.
What we run ourselves. Our own orchestrator is on Render, but it is deployed from source with Render’s native Node buildpack (
rootDir: apps/orchestrator, tsx src/index.ts) rather than from this image — the Vercel deployment guide has that recipe. The image packages the same service; it is not yet the thing that has the most production hours on it. If you hit a rough edge self-hosting it, tell us — that feedback is more useful to us than a workaround is to you.Resource requirements
- Memory: ~300–500 MB at idle, ~600–800 MB under typical load. A 1 GB instance is comfortable; the smallest “$7/month-ish” tier on most hosts is enough for early use.
- CPU: Mostly I/O-bound — the orchestrator spends most of its time waiting on LLM API calls, not computing. 0.5 vCPU is fine for a handful of concurrent sessions; 1 vCPU is comfortable for a small team.
- Disk: A persistent volume for
/app/.data(session state, telemetry, generated images). 1–5 GB is plenty for early use; generated-image storage is what grows fastest if you use AI image generation heavily. - Network: Outbound HTTPS to your chosen LLM providers (
api.anthropic.com,api.openai.com,generativelanguage.googleapis.com); inbound HTTPS from your editor and site origins. No inbound from end-users — only your editor and Next.js site need to reach it.
Critical hosting constraints
A few things matter regardless of which host you pick:- Persistent volume is required. The orchestrator stores its SQLite database at
/app/.data/orchestrator.db(plus-wal/-shmsidecar files and rolling backups). If you mount that path on an ephemeral filesystem (Cloud Run without a volume, Heroku-style ephemeral dynos, default container hosts without disk attachment), every redeploy or container reschedule will wipe all sessions and undo history. Always attach a persistent volume — even 1 GB is enough. - SSE-friendly reverse proxy. The chat endpoint streams server-sent events for live editor updates. Some reverse proxies and CDNs buffer responses by default, which makes the editor look frozen until the full response lands. If you put a reverse proxy in front of the orchestrator, disable response buffering on
/chat/*— and on/sites-agent/*if you turn that surface on (in nginx:proxy_buffering off; in Caddy:flush_interval -1onreverse_proxy; in Cloudflare: bypass cache for these paths). - Long timeouts, if you enable the agent surface. Chat turns finish in seconds, so a default 30s or 60s request timeout is survivable for editing alone. Sites-agent runs (full URL migration, repo integration) take several minutes and will be killed mid-run — so if you set
AGENT_SURFACE=on(see The agent surface below), raise request timeouts to at least 10 minutes on the orchestrator’s service. - CORS for the editor’s origin. Set
ORCHESTRATOR_CORS_ORIGINSto include both your site and editor origins (HTTPS, no trailing slash). See CORS configuration below. - Public HTTPS reachable from your editor and site. Both the editor (browser) and your Next.js site (server-side draft fetches) need to call the orchestrator. If your editor is on
https://editor.example.comand your site is onhttps://www.example.com, the orchestrator needs to be on a URL both can reach — usually a public HTTPS endpoint likehttps://orchestrator.example.com.
Building the image
The orchestrator source lives atapps/orchestrator in the repo. Build the image from the repository root:
packages/shared, packages/migration-sdk, packages/orchestrator-core, packages/richtext). It needs BuildKit, which is the default in Docker 23 and later — the Dockerfile’s # syntax directive requires it, and so does the context ignore list, which lives at apps/orchestrator/Dockerfile.dockerignore because the build context is the repo root.
The runtime stage is node:22-slim rather than Alpine on purpose: better-sqlite3 ships prebuilt binaries against glibc, and on musl the install falls back to compiling from source with a toolchain the slim Node images don’t carry into production. The build stage installs python3, make and g++ as a fallback for targets with no prebuild; they never reach the final image.
Running standalone
Required environment variables
At minimum you need one AI provider key:CORS configuration
With
ORCHESTRATOR_CORS_ORIGINS unset, the orchestrator falls back to four development origins: http://localhost:3000, http://127.0.0.1:3000, http://localhost:4100 and http://127.0.0.1:4100. localhost and 127.0.0.1 are distinct origins to a browser, which is why both spellings are listed. For production, set the allowed origins explicitly — the fallback is replaced, not extended.State persistence
The orchestrator writes session state, telemetry, and generated images to/app/.data inside the container. Mount a volume there to persist data across restarts.
The image pre-configures these paths:
ORCHESTRATOR_DB_FILE=/app/.data/orchestrator.db— the live state, plus its-wal/-shmsidecars and rolling.db.backup-<ts>snapshotsORCHESTRATOR_STATE_FILE=/app/.data/orchestrator-state.json— legacy, and only meaningful if you are carrying over a volume written before the SQLite store. Nothing creates this file todayCHAT_TELEMETRY_FILE=/app/.data/chat-telemetry.ndjsonORCHESTRATOR_GENERATED_IMAGE_DIR=/app/.data/generated-images
The agent surface, off by default
The container setsNODE_ENV=production, and in production the orchestrator does not mount /agent/* or /sites-agent/* — the routes behind site onboarding, URL migration and repo integration. Calls to them return 404 until you opt in.
That is deliberate rather than an oversight. Those routes run open-ended multi-turn agent loops with file and shell tools; the useCliAgent variant spawns the Claude CLI with --permission-mode bypassPermissions and passes the request body through as the prompt. That is arbitrary code execution as the orchestrator’s process user, by design — it is what makes onboarding work — so it cannot be made safe by narrowing what it may run. It can only be kept off and put behind a credential.
To turn it on you need both of these:
AGENT_SURFACE=on with neither credential set refuses to mount and says why in the boot log rather than mounting an open surface. AGENT_CLI=1 is a separate opt-in, off everywhere by default, for the variant that spawns the CLI on the host.
If you only need chat editing and publishing, leave all of this alone — /chat, /ops and /publish are unaffected.
Using docker-compose
Adocker-compose.yml at the repo root runs the orchestrator with sensible defaults:
orchestrator-data) and loads env vars from .env at the repo root. That env_file entry is not optional: with no .env present, docker compose up fails before it starts anything. Copy .env.example and put at least one provider key in it first.
Health check
The container includes a health check that pollshttp://127.0.0.1:4200/health every 30 seconds. Check status with:
Environment reference
See.env.example at the repo root for the complete list of environment variables. Common Docker overrides:
Running locally without Docker
For local development you can run the orchestrator directly via pnpm from the repository root — that’s the faster dev loop. Docker is the supported path for production self-hosting; the source-based workflow is for anyone iterating on the orchestrator itself.Troubleshooting
Container exits immediately
Check logs:docker logs avocado-orchestrator. The most common cause is missing API keys or an invalid .env file.
CORS errors from editor or site
SetORCHESTRATOR_CORS_ORIGINS to include both the site and editor origins (no trailing slashes). Remember that setting it replaces the localhost defaults, so a value that lists only your production site will lock out a locally-running editor.
/sites-agent/* returns 404
Expected in a container: the agent surface is unmounted under NODE_ENV=production unless you opt in. See The agent surface. The boot log says which decision was made and why — look for [agent-surface].
State not persisting
Ensure the volume is mounted at/app/.data. The image runs as root, so permissions are rarely the problem — check that the mount actually landed (docker inspect -f '{{json .Mounts}}' avocado-orchestrator) and that ORCHESTRATOR_DB_FILE still points inside it if you overrode it.
Health check failing
Wait for the 10-second start period. If it still fails, check that the orchestrator is listening on0.0.0.0:4200 (it should be by default) and that no firewall is blocking the port.