Deploy
Topology
| Component | Host | Notes |
|---|---|---|
API (apps/api) | Railway | Containerized via the project’s Nx webpack build. |
Web (apps/web) | Vercel | Next.js 16 app; vercel.json at repo root selects the project. |
Admin (apps/admin) | Vercel | Standalone 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) | Vercel | Astro multi-tenant; custom domains attached programmatically via Vercel API. See VERCEL_TOKEN, VERCEL_MINISITES_PROJECT_ID. |
| Postgres | Managed (Railway) | pgvector extension required. |
| Redis | Managed | Used for BullMQ, Socket.IO adapter, Spotter rate limit. |
| R2 | Cloudflare | Two 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 dispatch1. 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
mainand builds the api project. Thenx build apioutput is shipped to a Node container. - Web — Vercel watches
mainand buildsapps/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:migrateSee migrations.md for the full discipline. Migration must be approved by the owner.
5. Smoke
After Railway/Vercel swap:
GET https://<api>/health→ 200GET 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
| What | How |
|---|---|
| API | Railway → previous deployment → “Roll back”. |
| Web | Vercel → previous deployment → “Promote to production”. |
| DB | No 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_URLCLERK_SECRET_KEY(prod instance,sk_live_*)CLERK_WEBHOOK_SECRET(prod webhook endpoint)PAYMENT_CREDENTIALS_ENCRYPTION_KEY,NATIONAL_ID_ENCRYPTION_KEY— see credentials-rotation.mdINVITE_SECRET,QR_CHECKIN_SECRETCARDCOM_*real terminal,PLATFORM_BILLING_*Taikan’s own Cardcom credentials,MORNING_*prod hostsR2_*prod bucket credentials,R2_COMPLIANCE_BUCKET_NAME(object-locked)VERCEL_TOKEN,VERCEL_MINISITES_PROJECT_ID— minisite domain provisioningSENTRY_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 observabilityCRONS_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-testidthat’s referenced inapps/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.