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 / Neon — see ops)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 → main → CD Full Test Gate → Railway/Vercel auto-deploy → manual migration → smoke

1. 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 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.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

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/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_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_* FitKit’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.