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:
| Vercel | Railway | |
|---|---|---|
Serves 5crse.com, www.5crse.com, /admin | yes | no |
| Reachable at | the public domains | 5crse-production.up.railway.app |
| Owns environment configuration | its own project env | the railway CLI env |
| Runtime | serverless functions | long-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:statusis 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.
| Variable | Description |
|---|---|
DATABASE_URL | Neon connection string |
POSTGRES_URL, POSTGRES_URL_NON_POOLING | Compatibility / direct connection |
PAYLOAD_SECRET | Payload auth secret; also signs unsubscribe tokens |
BLOB_READ_WRITE_TOKEN, BLOB_PUBLIC_BASE_URL | Vercel Blob media storage |
PAYLOAD_PUBLIC_SERVER_URL, NEXT_PUBLIC_PAYLOAD_URL | Deployment URL |
RESEND_API_KEY, RESEND_FROM | Transactional + newsletter email |
PROMO_COPY_TO | Optional; oversight copy of promo sends (defaults to vincent@5crse.com) |
CRON_SECRET | Guards the cron routes |
OPENROUTER_API_KEY, GEMINI_API_KEY | AI assistant chat (via OpenRouter) and image generation |
OPENROUTER_MODELS_SIMPLE, _STANDARD, _COMPLEX | Optional; per-tier OpenRouter model lists, primary first (turns are routed by task complexity) |
OPENROUTER_MODELS | Optional kill switch; pins every tier to one list |
OPENAI_API_KEY, OPENAI_MODEL | Assistant fallback, used only when the OpenRouter key is unset |
STRIPE_SECRET_KEY, NEXT_PUBLIC_STRIPE_KEY | Payments |
TICKETMASTER_API_KEY | Ticketmaster Discovery API |
SENTRY_DSN | Error 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
- Merge to
main— Vercel and Railway both build automatically. - Run
pnpm migrate:rwif the change carries a migration. - 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.
