Skip to Content
Living documentation — last reviewed 2026-05-28
RunbooksDeploy

Deploy

Topology

ComponentHostNotes
API (apps/api)RailwayContainerized via the project’s Nx webpack build.
Web (apps/web)VercelNext.js 16 app; vercel.json at repo root selects the project.
Admin (apps/admin)VercelStandalone Vite app; apps/admin/vercel.json.
Marketing (apps/marketing)Railway (per apps/marketing/railway.toml)Astro static + a tiny server for previews.
Minisites (apps/minisites)VercelAstro multi-tenant; custom domains attached programmatically via Vercel API. See VERCEL_TOKEN, VERCEL_MINISITES_PROJECT_ID.
PostgresManaged (Railway)pgvector extension required.
RedisManagedUsed for BullMQ, Socket.IO adapter, Spotter rate limit.
R2CloudflareTwo buckets: general (R2_BUCKET_NAME) and compliance (R2_COMPLIANCE_BUCKET_NAME, used for FIT-158 signed PDFs with object-lock).

Promotion path

PR (smoke + e2e gates) → main → Railway/Vercel auto-deploy → manual migration → smoke ↑ CD Full Test Gate validates main on manual dispatch

1. PR opened

ci-smoke-tests.yml runs:

  • DB migration against a fresh test DB.
  • API unit + integration + e2e (skipped for web-only PRs).
  • Web unit + integration (skipped for api-only PRs).
  • Playwright web E2E (separate job, runs once smoke is green).

Required to pass before merge.

2. Merge to main

cd-full-test-gate.yml re-runs the full suite (including Playwright web E2E) against main on manual dispatch. PR checks already ran the same suites against the merge commit, so this is only needed when that assumption breaks: risky deploys, direct pushes to main, or a merge whose checks ran against a stale base. A red run means main is broken and blocks further deploys.

deploy-pr-preview.yml builds a preview environment for review.

3. Auto-deploy

  • API — Railway watches main and builds the api project. The nx build api output is shipped to a Node container.
  • Web — Vercel watches main and builds apps/web.
  • Admin / minisites / marketing — same pattern, per project.

4. Migrate the database

Migrations are not run automatically by the application on boot. Run them as a deliberate step before Railway swaps to the new API image:

# from a machine with prod DATABASE_URL (or DIRECT_DATABASE_URL) pnpm db:migrate

See migrations.md for the full discipline. Migration must be approved by the owner.

5. Smoke

After Railway/Vercel swap:

  • GET https://<api>/health → 200
  • GET https://app.taikan.fit/en → 200, locale set, Clerk session loads
  • Sign in as a test member → dashboard renders
  • Spotter chat dock opens (coach persona) → first turn returns

If any step fails, see incident-response.md.

Rollback

WhatHow
APIRailway → previous deployment → “Roll back”.
WebVercel → previous deployment → “Promote to production”.
DBNo down-migrations. Either forward-fix or restore from the managed snapshot (Railway backups). See migrations.md.

Environment variables in prod

Set in Railway (API, marketing) and Vercel (web, admin, minisites) project settings. Never check production keys into the repo or .env.example — the file holds only sandbox/test placeholders.

Key production-only vars:

  • DATABASE_URL (PgBouncer pool) + DIRECT_DATABASE_URL (direct, for migrations)
  • REDIS_URL
  • CLERK_SECRET_KEY (prod instance, sk_live_*)
  • CLERK_WEBHOOK_SECRET (prod webhook endpoint)
  • PAYMENT_CREDENTIALS_ENCRYPTION_KEY, NATIONAL_ID_ENCRYPTION_KEY — see credentials-rotation.md
  • INVITE_SECRET, QR_CHECKIN_SECRET
  • CARDCOM_* real terminal, PLATFORM_BILLING_* Taikan’s own Cardcom credentials, MORNING_* prod hosts
  • R2_* prod bucket credentials, R2_COMPLIANCE_BUCKET_NAME (object-locked)
  • VERCEL_TOKEN, VERCEL_MINISITES_PROJECT_ID — minisite domain provisioning
  • SENTRY_DSN, SENTRY_RELEASE=$RAILWAY_GIT_COMMIT_SHA, SENTRY_AUTH_TOKEN (build-time, for source-map upload)
  • POSTHOG_API_KEY, POSTHOG_PROJECT_ID — server-side event ingestion + admin observability
  • CRONS_ENABLED=true — opt scheduled jobs back in (off by default in .env.example)

Deploy gates

A PR should not be merged if:

  • CI is red.
  • The PR adds a migration but the description doesn’t explain rollback.
  • The PR removes a data-testid that’s referenced in apps/web/e2e/.
  • The PR adds a new env var without updating .env.example.
  • The PR adds a user-facing string without keys in all three of en.json, he.json, ru.json.

These are conventions, not enforced gates — code review is the gatekeeper today.