Skip to main content

Deployment Topology

Three Next.js apps on Vercel, one worker fleet on Fly.io, orchestrated by Temporal Cloud against Supabase Postgres.
Production and staging run different worker topologies. Production is one large process running the whole fleet; staging is four right-sized process groups. Do not reason about one from the other.

Production — monolithic workers

Staging — split worker groups

Same Vercel and data layer; the worker fleet is split into four right-sized processes.

Fly configs

Five files. Only two are live.
fly.split.prod.toml targets the same app name as the live fly.prod.toml. A flyctl deploy -c fly.split.prod.toml would silently replace the production fleet with a different topology. Deleting or renaming the unused configs would remove the risk.
All configs share: rolling deploy strategy, HTTP health check on :8080/health every 30s, restart = always, SIGTERM with kill_timeout = 300 (worker force deadline is 4m30s, leaving 30s for connection, logger, and telemetry cleanup).

Worker groups

WORKER_GROUP selects a subset at startup (apps/temporal-workers/src/workers/index.ts:61-74). Unset starts the whole fleet — which is exactly what production does.
apps/temporal-workers/WORKER_GROUPS.md lists imports at 18. That count is stale — it omits xentral. The code produces 19. This matters because the same table derives the Prisma client-slot budget (43 for imports, 62 for a full split set). Recompute it before any cutover to the split production model.

Release paths

The two halves of the platform ship differently — the apps automatically, the workers by hand. Vercel deploys from main, prod, stage, release/*, rc/*, and feature/*; everything else is blocked. Identical policy across all three apps. Nothing in .github/workflows/ references Fly. Worker deploys are manual flyctl, which is why they are drawn as a separate path. Database migrations run via the migrate-db.yml workflow.

Environments

Staging additionally sets OBSERVABILITY_PROBE_ENABLED = 1; production sets it to 0.

Notes

Region is fra for workers. Vercel regions are not pinned in any vercel.json. Secrets are Fly secrets, not config. fly.prod.toml documents NEXT_PUBLIC_AXIOM_TOKEN, UPLOADTHING_TOKEN, and ENCRYPTION_KEY as fly secrets set values — they are deliberately absent from the committed config. Workers expose no public port. The :8080/health endpoint is an internal Fly Machine check; the workers are not behind Fly Proxy. Connection pooling is the scaling constraint. WORKER_GROUPS.md documents Supavisor transaction-mode pools per group, with the accounting process additionally using a lazy prismaDirect pool capped at ACCOUNTING_QUEUE_LANES * 6 + 4.