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 the same split topology — four independently deployable process groups. This changed in #3243 (chore(temporal): make split production config canonical), which promoted the split config to fly.prod.toml and demoted the old monolith to fly.legacy.prod.toml.

Runtime topology

The four process groups are sized independently: These are committed defaults, not an inventory of currently started Machines. See Worker groups below for worker counts. Staging is identical in shape; it differs only in app name, Axiom dataset suffix, OBSERVABILITY_PROBE_ENABLED = 1, --ha=false, and the extra test-document worker in misc.

Fly configs

Five files. Two are canonical; two are rollback-only; one is superseded.
The earlier footgun is closed. A bare pnpm deploy now fails on purpose with “Use a deploy:prod:<group> script; deploy:prod:legacy is rollback-only”, and the monolith config is named legacy rather than shadowing the canonical one.

Deploying

Per-group, one command each — the whole point of the split is that imports can be redeployed without disturbing accounting.
Available: deploy:prod:{imports,accounting,reconciliation,misc} and the same for deploy:staging:*. Each resolves to fly deploy -c fly.prod.toml --process-groups <group>. Rollback to the monolith is deploy:prod:legacy, which targets fly.legacy.prod.toml --process-groups app. Staging additionally passes --ha=false. 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). Each Fly process group sets it via dist/worker-group.js <group>. Unset starts the whole fleet — now only local development and the legacy rollback path.
The defaults in WORKER_GROUPS.md include Xentral: imports has 19 Workers, 132 Activity slots, 1,350 sticky cache slots, and 44 regular Prisma client slots. A complete split set has 63 regular client slots in production and 64 in staging; two production Machines per group have 126. The lazy primary-routed pools are additional. These are client concurrency ceilings, not reserved Postgres sessions. Runtime sizing comes from packages/temporal-config/src/concurrency.ts.

Release paths

The two halves of the platform ship differently. Vercel automatic Git deployments are enabled for prod and stage across webapp, portal, and console. Other branches, including main, release/*, rc/*, and feature/*, are blocked to avoid preview build costs. The Ignored Build Step policy allows VERCEL_ENV=production or VERCEL_GIT_COMMIT_REF=stage. Vercel uses exit code 1 to build and 0 to skip. The webapp and portal scripts retain this policy in the repository; all three projects also use the equivalent guard in their Vercel project settings. The project-wide preview deployment disable switch must remain off so staging can deploy; the branch allowlist and build guard block ordinary previews, including builds from older branches with permissive vercel.json rules. Automatic worker deploys follow the canonical migration workflow after cutover. migrate-db.yml verifies the target and complete migration manifest, then calls reusable deploy-workers.yml through dependent jobs. The worker job runs only when repository variable WORKERS_GHA_DEPLOY is true, using the existing branch-restricted staging and Production environments. Before activation, Anton must restrict staging to stage only and Production to prod only. Freeze prod merges during cutover. Disable both native Fly GitHub auto-deploys, configure the corresponding environment secrets, then enable the variable and verify the production image commit matches the frozen prod head (deploy that head if needed) before unfreezing prod merges. Push to staging to verify its commit label. See the deployment identity cutover for the ordered prerequisites, current-head rerun guard and image rollback. The per-group pnpm scripts are intentional manual bypasses and omit commit build args. Protected environment target pins are separately required before the new automatic migration/worker owner can run. An empty resolved pin fails closed, but a missing environment definition can fall back to repo/org scope; separate environment-only metadata verification is required before merge or activation.
This source gate orders automatic workers only. Vercel readiness checks are not configured by this change. Stage/custom-preview activation, direct URLs, cron/manual/Force Promote paths and native Fly/manual deployment bypasses remain release acceptance gates. PR/candidate builds remain independent of DB.

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.