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 / Neon — see ops) | 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 → main → CD Full Test Gate → Railway/Vercel auto-deploy → manual migration → smoke1. PR opened
ci-smoke-tests.yml runs:
- DB migration against a fresh test DB.
- API unit + integration + e2e.
- Web unit + integration.
Required to pass before merge.
2. Merge to main
cd-full-test-gate.yml runs the full suite including Playwright web E2E. Required to pass before any deploy.
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.fitkit.io/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/Neon 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_*FitKit’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.