Reference

Deploying

One decision dominates every other on this page: the kernel is never public. It holds every credential you have — model keys, the Composio key, the vault master key — and its /v1 surface trusts whoever presents a project key. If it is reachable from the internet, nothing else here matters.

  founders  ──► cloud / portal (public)  ──┐
                                            ├──► kernel (PRIVATE) ──► Postgres
  customers ──► your product   (public)  ──┘

Private subnet, security group admitting only those two services. Everything below assumes that is true.

What has to change from your laptop

SetOr else
MYCEL_DATABASE_URLEverything is in memory and every task runs in the API process. The banner says store=memory queue=inline
MYCEL_SECRET_KEYEvery stored credential becomes unreadable at the next restart
MYCEL_API_KEY, MYCEL_OWNER_*Regenerated on every boot, so your product and your login break on every deploy
MYCEL_SANDBOX=docker (or daytona)local runs agent commands on the host. It is not an isolation boundary
MYCEL_PUBLIC_URLThe sandbox cannot call back to the harness, so gates and the action proxy fail

MYCEL_SECRET_KEY is the one you cannot lose. 32 bytes, base64. It encrypts every connection credential. Lose it and every customer re-authorises from scratch; rotate it and the same thing happens. Back it up somewhere that is not the same account as the database — a database backup without that key is unreadable ciphertext.

Database

Set MYCEL_DATABASE_URL and the kernel migrates on boot: tasks, events, the service surface, tenants, the audit chain and the vault all become durable. graphile-worker creates its own schema in the same database. One datastore, one pool, no broker.

If you are using a pooler, use the session pooler, not the transaction pooler. The kernel issues real transactions — FOR UPDATE SKIP LOCKED in the scheduler, and the row lock that allocates audit sequence numbers. The transaction pooler supports neither, and the failure is subtle rather than loud.

Scale workers, not the API

Both are the same image. A worker just has no port and no load-balancer target.

  • MYCEL_WORKER=0 — an API-only replica, serving HTTP and SSE
  • MYCEL_WORKER_CONCURRENCY (default 10) — concurrent runs per worker

Runs are what cost money and what saturate first: each concurrent run holds a sandbox, so memory is the limit. Roughly 120–300 runs per hour per 2GB worker at the default concurrency.

Let workers overlap during a rolling deploy. graphile-worker drains in-flight jobs on SIGTERM, so an overlapping replacement finishes what it started rather than abandoning a run mid-approval.

Health checks: GET /health on the kernel.

Running more than one kernel replica

The queue, the store, the audit chain and the scheduler are all multi-instance clean. Several smaller things are not, and they are all in process memory:

Process-localConsequence
Approval TTL timersA restart orphans pending rows; nothing sweeps expires_at
Cancel registryPOST /v1/tasks/:id/cancel only works on the replica running the task
Policy countersmax_per_day is effectively multiplied by the replica count
Portal links and sessionsCustomers are signed out on restart, and links break across replicas
Idempotency mapIdempotency-Key only deduplicates within one process
Restart recoveryA booting replica fails every unfinished task, including ones running elsewhere

Approvals themselves survive this: the blocked run polls the approval row every 700ms, so a decision landing on another replica still unblocks it. But until the list above is backed by shared state, the supported shape is one kernel replica plus additional workers.

Sandboxes in production

docker needs a Docker socket the kernel can reach and MYCEL_PUBLIC_URL set to something resolvable from inside a container — often http://host.docker.internal:4000. daytona needs DAYTONA_API_KEY and a publicly resolvable MYCEL_PUBLIC_URL, and is the least exercised of the three.

Whichever you choose, confirm it in the boot banner. An unrecognised MYCEL_SANDBOX value falls back to local without an error.

What is not done

  • The Terraform in infra/ has never been applied. It is written from verified container behaviour — both app images build and run against a live kernel — but no AWS account has seen it. Expect the usual first-apply friction: a certificate in the wrong region, health-check grace periods, service discovery timing.
  • No mail sender in the kernel. Password resets and client portal links return a token for your product to deliver. Nothing is posted for you.
  • No per-customer provisioning. Standing up an app instance per customer is manual.
  • No image build pipeline, WAF, or Redis in the shipped infrastructure. Redis is only needed for a second kernel replica.

Read these in the repo

  • DEPLOY.md — the full AWS walk-through, service by service
  • COST.md — a real monthly estimate and what breaks first
  • infra/ — Terraform, with an honest status section at the top

What to read next

Limitations & roadmap — the rest of what is not built, in one place.