Skip to content

Deploy pipeline

How code reaches staging/prod. Source of truth: .github/workflows/ci.yml. For the runtime shape (edge, ECS, CDN) see Infrastructure.

How a deploy happens

Triggers: push to main only (+ workflow_dispatch). PR runs are disabled to save Actions minutes — validate locally with bun run typecheck && lint && format:check && test.

The jobs — .github/workflows/ci.yml

JobGateWhat it does
buildalwaysPostgres 17 service → bun install --frozen-lockfile, typecheck, lint, format:check, test. Guards the edge worker stays Node-free (wrangler deploy --dry-run + grep for node:*/pino).
deploymainMigrate Neon → deploy the Worker (wrangler deploy) → set Worker DATABASE_URL secret. (Path-B step sets ORIGIN_URL+EDGE_SECRET when PATH_B=true.)
origin-imageDEPLOY_AWS=trueBuild the origin Docker image → push to ECR ratio-3-0-origin (:sha, :latest). A nested ECS-Express roll (when ECS_DEPLOY=true) re-registers the task def with the new image + env and canaries it in.
admin-uialways (deploy gated)Build/test apps/admin-web; deploy to Cloudflare Pages (ratio-admin) when DEPLOY_ADMIN_UI=true.
purgePURGE_CACHE=truePurge the ratiodev.in Cloudflare zone.

Gates live in GitHub, not the repo

What actually deploys is controlled by GitHub repo-variable gates (DEPLOY_AWS, ECS_DEPLOY, PATH_B, PURGE_CACHE, DEPLOY_ADMIN_UI) — their values live in GitHub settings, not the repo, so read the gate column as "runs when its variable is set", not as a statement of which are on.

Origin rollout — canary + auto-rollback

When ECS_DEPLOY=true, the new origin image doesn't go straight to 100%. It rolls out gradually through ECS Express's managed canary, and rolls back on its own if health regresses:

  • Uses the Express-native update-express-gateway-service (not the classic update-service, which tangles Express).
  • Origin + admin-api are one image — the role is chosen at runtime by RATIO_SERVICE (unset → origin; admin-api → control plane).
  • Two rollback kinds: code (redeploy, minutes) vs the content/live-theme pointer (a flip, seconds).

Adding a workspace? Update the Dockerfile.

The origin image hand-copies each workspace's package.json before bun install --frozen-lockfile. A new workspace not in that copy-list breaks the origin build. (This is why apps/docs is deliberately excluded from the workspace set — "!apps/docs" in the root package.json — so its VitePress deps never touch the app lockfile or the origin image.)