Ultimate

Deploy recipes

One section per host. The framework ships no host config file, no generator and no adapter: every host below builds the same docker/Dockerfile and runs the same image once per role. A recipe is the handful of settings that host asks for, and nothing more. A host joins this page by a PR that ran it, and each section says whether it was executed or not executed, with its date.

What every host needs, whatever its words for it:

SettingValue
build sourcedocker/Dockerfile, context the app root
portthe host injects PORT; web binds exactly it, every interface
health path/readyz (may it take traffic) — never a TCP check
release commandthe image's own entry with ROLE=migrate: env ROLE=migrate bun apps/web/server.ts, exit 0 before any role serves
servicesone per role from the one image, ROLE set on each: web, worker, scheduler; sync only once realtime is on (Deployment)
environmentULTIMATE_ENV=production, DATABASE_URL, ULTIMATE_CURSOR_SECRET, STORAGE_SIGNING_SECRET (unless S3_ENDPOINT and S3_BUCKET are set), SESSION_SECRET, APP_URL — x env init --production writes them all with fresh secrets; copy the values into the host's secret store. NODE_ENV=production x env check --json asks what a deployed boot would refuse

One box, Compose

As of 2026-11-03 — executed, and on every push: the compose-deploy CI job runs docker/deploy-proof/compose.sh on a fresh scaffold.

x env init --production         # .env.production: secrets generated, gitignored, mode 600
x build --target docker         # ultimate-app:dev
x deploy                        # migrate, then web, worker, scheduler; backfill last
curl -fsS http://127.0.0.1:3000/readyz
SettingHere
build sourcex build --target docker; --image names any other tag
port3000:3000 published by web, 3001:3001 by sync
health patheach service's healthcheck: /readyz for web/sync, /metrics for the rest
release commandthe migrate service, run --rm; every role waits on service_completed_successfully
servicesdocker/docker-compose.prod.yml, one per role
what it losesnothing to a free tier — it is your box. web and sync stay at one replica each: one host port, one binder

APP_URL and SYNC_URL are written for localhost: set the host your users reach before they do.

Kubernetes, the chart

As of 2026-11-03 — executed: the deploy-proof CI job installs a scaffold into kind with x deploy --method helm and upgrades it under load with zero failed requests.

SettingHere
build sourceyour registry; x deploy --method helm --image <repo>:<tag>
portthe chart's per-role Service; ingress.host sets APP_URL on sync
health pathstartupProbe, readinessProbe on /readyz, liveness on /healthz
release commandthe chart's migrate Job, a pre-install/pre-upgrade hook
servicesone Deployment per role, an HPA each
what it losesnothing to a free tier; the cluster is the cost

Fly.io

As of 2026-11-03 — not executed.

SettingHere
build sourcefly launch --no-deploy --dockerfile docker/Dockerfile; the fly.toml it writes is yours
port[http_service] internal_port = 8080 and [env] PORT = "8080"
health path[[http_service.checks]] path = "/readyz"
release command[deploy] release_command = "env ROLE=migrate bun apps/web/server.ts"
services[processes] is a command per group, and the image's ENTRYPOINT reads ROLE: one Fly app per role (fly apps create <app>-worker), each with ROLE in [env]
what it losesmachines that stop when idle stop worker and scheduler with them (Tutorial 5)

Render

As of 2026-11-03 — not executed.

SettingHere
build sourcea Docker web service, Dockerfile path docker/Dockerfile, context .
portRender injects PORT
health pathHealth Check Path /readyz
release commandPre-Deploy Command env ROLE=migrate bun apps/web/server.ts — a paid instance type only
servicesa web service with ROLE=web; a background worker per other role, same repository and Dockerfile
what it losesa free web service sleeps when idle, and free has no background worker and no pre-deploy command: run the migrate one-off yourself before promoting

Railway

As of 2026-11-03 — not executed.

SettingHere
build sourceRAILWAY_DOCKERFILE_PATH=docker/Dockerfile on each service
portRailway injects PORT
health pathHealthcheck Path /readyz
release commandPre-Deploy Command env ROLE=migrate bun apps/web/server.ts on the web service
servicesone service per role from the same repository, ROLE set on each
what it losesthe trial's credit, then usage billing; a service with serverless sleep on stops worker and scheduler with it

Heroku

As of 2026-11-03 — not executed. Heroku has had no free dynos since 2022-11-28.

SettingHere
build sourceheroku stack:set container, then a heroku.yml with build: { docker: { web: docker/Dockerfile } }
portHeroku injects PORT
health pathnone on the router; the drain still answers /readyz for a check you run
release commandrelease: { image: web, command: ['env', 'ROLE=migrate', 'bun', 'apps/web/server.ts'] } in heroku.yml
servicesone process type per role in run:, each env ROLE=<role> bun apps/web/server.ts
what it losesno free tier at all

Bare VM, no container runtime

As of 2026-11-03 — not executed as a deploy. The windows CI job boots a scaffold's binary and gets 200 from /healthz; no scaffold has been served from a VM this way (Tutorial 5, Bare VM).

SettingHere
build sourcex build --target binary --out .x/app, started from the app root with the source beside it
portPORT in each unit's environment
health path/readyz, from your proxy or monitor
release commandROLE=migrate .x/app once, before the units restart
servicesone systemd unit per role, Environment=ROLE=<role>
what it losesthe image's reproducibility: the box's Bun and node_modules are the runtime

Edit this page on GitHubThis page as markdown