Skip to main content

Production Deployment Checklist

Checklist for deploying Syntropy Journals to production on Railway.

Architecture

GitHub (main branch)
  → GitHub Actions CI/CD (.github/workflows/deploy.yml)
    → Railway.app (split architecture)
      ├── syntropy-portal  (frontend-only, PORT=3000)
      └── syntropy-api     (backend-only, PORT=8000)

External Services:
  ├── Clerk          (auth, syntropyhealth.bio)
  ├── Supabase       (PostgreSQL, prod instance)
  ├── Zilliz Cloud   (vector DB, Milvus)
  ├── Stripe         (payments)
  ├── PostHog        (analytics)
  ├── Brevo          (transactional email)
  ├── OpenRouter     (LLM provider)
  └── Shopify        (protocol marketplace)

Branch → Environment Mapping

BranchEnvironmentGates
mainprodHard (all checks must pass)
testtestSoft (continue-on-error for non-prod)
dev-*testSoft

Pre-Deploy Checklist

1. DNS Records (Cloudflare — syntropyhealth.bio)

Clerk Email (DKIM + SPF) — proxy OFF required

These CNAME records must have Cloudflare proxy OFF (grey cloud / DNS only) — Clerk needs direct DNS resolution for DKIM verification.
TypeNameTargetProxyStatus
CNAMEclkmailmail.c2o0686k3n21.clerk.servicesOFFAdd
CNAMEclk._domainkeydkim1.c2o0686k3n21.clerk.servicesOFFAdd
CNAMEclk2._domainkeydkim2.c2o0686k3n21.clerk.servicesOFFAdd

Clerk Account Portal — proxy OFF for Clerk verification

TypeNameTargetProxyStatus
CNAMEaccountsaccounts.clerk.comOFFVerify
Note: The Frontend API uses proxy mode (see below) — no clerk.* CNAME is needed. All Clerk JS requests route through https://syntropyhealth.bio/__clerk.

2. Clerk Dashboard Setup (clerk.com)

2a. Domain & Proxy Configuration

Clerk’s Frontend API must route through your own domain so OAuth callbacks, session management, and sign-in all use syntropyhealth.bio (not a Clerk subdomain). Steps:
  1. Go to Clerk DashboardDomains
  2. In the Frontend API section, click Set proxy configuration
  3. Enter the proxy URL: https://syntropyhealth.bio/__clerk
  4. Save — Clerk will verify the proxy is reachable (must be deployed first)
How it works:
Browser → https://syntropyhealth.bio/__clerk/* (your domain, Cloudflare proxy ON)
  → FastAPI route (clerk_proxy.py) adds required headers:
      Clerk-Proxy-Url: https://syntropyhealth.bio/__clerk
      Clerk-Secret-Key: sk_live_...
      X-Forwarded-For: <client IP>
  → Forwards to https://clerk.syntropyhealth.bio/* (Clerk's FAPI)
  → Response returned to browser
Code references:
  • Proxy route: syntropy_journals/app/api/routes/clerk_proxy.py
  • Provider config: page_wrapper.py passes proxy_url="/__clerk" to clerk_provider()
  • Activated by: CLERK_PROXY_ENABLED=true env var (prod only)

2b. Path & Redirect Configuration

SettingValue
Sign-in URL/sign-in
Sign-up URL/sign-up
After sign-in redirect/redirect
After sign-up redirect/redirect
SSO callback/sign-in/sso-callback
Account portalaccounts.syntropyhealth.bio

2c. Social Login (OAuth)

  • Google OAuth configured (minimum)
  • OAuth callback URLs will route through the /__clerk proxy automatically
  • Email verification enabled (after DKIM records propagate)

2d. Deployment Order for Proxy

The proxy must be live before Clerk can verify it:
  1. First deploy the app with CLERK_PROXY_ENABLED=true (Railway)
  2. Then set the proxy URL in Clerk Dashboard
  3. Clerk verifies https://syntropyhealth.bio/__clerk is reachable
  4. Once verified, all Clerk JS requests route through your domain

3. GitHub Actions Secrets

Required secrets in Settings > Secrets and Variables > Actions:
SecretPurposeStatus
RAILWAY_TOKENRailway deploy token (project-scoped)Set (2026-03-24)
OPENAI_API_KEYLLM + embeddingsSet
CLERK_SECRET_KEYClerk auth (prod sk_live_*)Set
STRIPE_SECRET_KEYStripe payments (live)NOT YET CONFIGURED — Stripe not onboarded
POSTHOG_PROJECT_API_KEYAnalytics write keySet (2026-03-24)
REFLEX_DB_URLSupabase prod connection stringSet
TEST_DB_URLSupabase test DB (integration tests)Set
SLACK_DEPLOY_WEBHOOK_URLDeploy notificationsSet
REDIS_URLUpstash Redis for Reflex state managerSet (2026-04-08)

4. Railway Environment Variables

Both syntropy-portal and syntropy-api services need these variables. They should mirror envs/prod + envs/base values. App Identity:
  • APP_ENV=PROD
  • FRONTEND_DEPLOY_URL=https://syntropy-portal-production.up.railway.app
  • REFLEX_DEPLOY_URL=https://syntropy-portal-production.up.railway.app
  • REFLEX_ACCESS_TOKEN=<from envs/prod>
Auth (Clerk + Proxy):
  • CLERK_PUBLISHABLE_KEY=pk_live_*
  • NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_live_*
  • CLERK_SECRET_KEY=sk_live_*
  • CLERK_AUTHORIZED_DOMAINS=https://syntropyhealth.bio
  • CLERK_PROXY_ENABLED=true — routes Frontend API through /__clerk
  • CLERK_PROXY_URL=/__clerk — (optional, this is the default)
Database:
  • REFLEX_DB_URL=<Supabase prod pooler URL>
Vector DB:
  • VECTOR_DB_TYPE=zilliz
  • ZILLIZ_CLOUD_URI=<from envs/base>
  • ZILLIZ_CLOUD_API_KEY=<from envs/base>
LLM:
  • OPENROUTER_API_KEY=<from envs/base>
  • OPENAI_API_KEY=<from envs/base>
Payments:
  • STRIPE_SECRET_KEY=<live key>
  • STRIPE_PUBLISHABLE_KEY=<live key>
  • STRIPE_WEBHOOK_SECRET=<live webhook signing secret>
  • BASE_URL=https://syntropyhealth.bio
Analytics:
  • POSTHOG_PROJECT_API_KEY=<from envs/base>
  • POSTHOG_HOST=https://us.i.posthog.com
State Manager (Redis):
  • REDIS_URL=rediss://...@open-dragon-87263.upstash.io:6379 — Upstash Redis (TLS)
  • Enables multi-worker state sharing and session persistence
  • Without this, Reflex uses disk-based state (single instance only)
  • Must use Upstash or a Redis with full ACL permissions (set/get/config/subscribe)
Email:
  • BREVO_API_KEY=<from envs/base>

5. Stripe Setup

  • Stripe account in live mode
  • Products and prices created matching app subscription tiers
  • Webhook endpoint configured: https://syntropyhealth.bio/api/stripe/webhook
  • Webhook events subscribed: checkout.session.completed, customer.subscription.*, invoice.*
  • Webhook signing secret saved to STRIPE_WEBHOOK_SECRET

6. PostHog Setup

  • Project created for production
  • POSTHOG_PROJECT_API_KEY is the write-only project API key
  • POSTHOG_HOST set to https://us.i.posthog.com

7. Database

  • Supabase prod instance accessible
  • Connection string uses pooler URL (port 6543)
  • Alembic migrations up to date: uv run alembic upgrade head
  • Catalog seeded: uv run python -m syntropy_journals.app.scripts.seed_data

Deploy Procedure

# 1. Ensure test branch is green
git checkout test
make test

# 2. Merge test → main
git checkout main
git merge test

# 3. Push to trigger CI/CD
git push origin main
# GitHub Actions: lint → unit tests → integration tests → Docker build → Railway deploy

# 4. Monitor deploy
gh run watch     # watch CI
# Railway dashboard → check service health

# 5. Verify
curl -s https://syntropy-portal-production.up.railway.app/ | head -5

Rollback

# Railway dashboard → Deployments → click previous deployment → Redeploy
# Or via Railway CLI:
railway service rollback

Env File Reference

FileCommittedPurpose
envs/templateYesBlank template for per-env files
envs/template.baseYesBlank template for shared base
envs/domains/_template.*YesBlank templates for integration domains
envs/baseNo (gitignored)Shared API keys across all envs
envs/devNo (gitignored)Local dev overrides
envs/testNo (gitignored)Test environment (Clerk test instance, Supabase test DB)
envs/prodNo (gitignored)Production (Clerk live, Supabase prod DB)