Deployment guide for 5CRSE — Vercel + Railway + Neon PostgreSQL

#Where things actually run

Every merge to main deploys to both hosts. They are not redundant, and they are not interchangeable:

VercelRailway
Serves 5crse.com, www.5crse.com, /adminyesno
Reachable atthe public domains5crse-production.up.railway.app
Owns environment configurationits own project envthe railway CLI env
Runtimeserverless functionslong-running container

Railway's service also lists 5crse.com in its domains, so railway domain reads as though Railway serves the site. It does not — DNS points at Vercel. Confirm at any time with:

curl -sI https://5crse.com/ | grep -i '^server:'

A green Railway deploy therefore does not mean the live site changed.

#Why both exist

Commit 45da4ff (2026-04-11) added the Dockerfile and railway.toml because Payload's admin init (24 collections, 36 endpoints, drizzle schema) "exceeds any lambda timeout" and wants a persistent Node process. The intended split was public site on Vercel, admin on Railway. That cutover was never completed — Vercel serves the Payload admin today.

Practical consequence: code in a Payload hook or route handler runs on serverless in production. Do not start background work with void promise() and assume it finishes; the function can freeze once the response is returned.

#Database

Production is Neon (serverless Postgres, US East 1), and both hosts point at the same instance — a schema change is immediately visible to both. The connection string is not recorded here; read it from Railway (railway run --service 5crse bash -c 'echo $DATABASE_URL') or the Vercel project env.

The adapter is @payloadcms/db-postgres with Drizzle, push: false, and prodMigrations: migrations (see src/payload.config.ts).

#Migrations

Migrations do not run during the build. The build script is build-portal.mjs && next build — no payload migrate anywhere in it, nor in the Dockerfile.

prodMigrations makes Payload run pending migrations at server initialization, which Payload documents as suitable for long-running servers. That describes Railway, not Vercel's per-request functions. Do not rely on it as the mechanism that ships your schema.

Run migrations explicitly, before or alongside the deploy:

pnpm migrate:rw

New globals, collections and array fields need a hand-curated migration — the CLI's auto-generation is unreliable on this schema. Write the file, register it in src/migrations/index.ts, and keep the list in date order.

migrate:status is not read-only. It applies pending migrations before printing its table. To inspect without writing, query the table directly:

railway run --service 5crse bash -c 'psql "$DATABASE_URL" -t -c "SELECT name, batch FROM payload_migrations ORDER BY batch;"'

#Environment variables

Anything a live request needs must exist in Vercel's project env — a value present only in Railway is absent from every request a customer makes. Anything local dev or a Railway job needs must exist in Railway. Most variables belong in both.

VariableDescription
DATABASE_URLNeon connection string
POSTGRES_URL, POSTGRES_URL_NON_POOLINGCompatibility / direct connection
PAYLOAD_SECRETPayload auth secret; also signs unsubscribe tokens
BLOB_READ_WRITE_TOKEN, BLOB_PUBLIC_BASE_URLVercel Blob media storage
PAYLOAD_PUBLIC_SERVER_URL, NEXT_PUBLIC_PAYLOAD_URLDeployment URL
RESEND_API_KEY, RESEND_FROMTransactional + newsletter email
PROMO_COPY_TOOptional; oversight copy of promo sends (defaults to vincent@5crse.com)
CRON_SECRETGuards the cron routes
OPENROUTER_API_KEY, GEMINI_API_KEYAI assistant chat (via OpenRouter) and image generation
OPENROUTER_MODELS_SIMPLE, _STANDARD, _COMPLEXOptional; per-tier OpenRouter model lists, primary first (turns are routed by task complexity)
OPENROUTER_MODELSOptional kill switch; pins every tier to one list
OPENAI_API_KEY, OPENAI_MODELAssistant fallback, used only when the OpenRouter key is unset
STRIPE_SECRET_KEY, NEXT_PUBLIC_STRIPE_KEYPayments
TICKETMASTER_API_KEYTicketmaster Discovery API
SENTRY_DSNError tracking (optional)

.env.example is reference only. Do not commit a populated .env.local; note that vercel link / vercel env pull will create one and will append .env* to .gitignore, which also ignores the tracked .env.example — revert that.

#Deploying

  1. Merge to main — Vercel and Railway both build automatically.
  2. Run pnpm migrate:rw if the change carries a migration.
  3. Verify the host that serves traffic, not the one that merely went green:
curl -s -o /dev/null -w '%{http_code}\n' https://5crse.com/admin

Cron schedules live in Railway, not in the repo.