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 thatimports can be
redeployed without disturbing accounting.
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 forprod 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.
Environments
Staging additionally sets
OBSERVABILITY_PROBE_ENABLED = 1; production sets it to 0.
Notes
Region isfra 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.