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. Worker deploys are not in .github/workflows/. WORKER_GROUPS.md documents Fly branch deployments using the canonical split configurations. The per-group pnpm scripts are for intentional manual deployments. Branch trigger settings live in Fly, so verify their enabled state and source branch there before a release. Database migrations run via the migrate-db.yml workflow.
Fly branch triggers and the migration workflow are not ordered relative to each other (WORKER_GROUPS.md). A future migration-gated deployment requires disabling the Fly branch trigger before moving deployment into the GitHub 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.