Skip to content

Phase 1.1 runbook — provision Supabase + wire Railway

Issue: #438 · Phase: 1.1 (½ day) · Completed: 2026-04-21

Status: complete for prod. Staging deferred to Phase 2.1 (no value in carrying an unused free-tier project through Phase 1 which is local-only). This file records how we did it and the gotchas to remember when staging gets provisioned later.

What landed

  • Supabase project gdqmtevlnumwixevcmgg, Pro plan, region us-east-1 (matches Railway app region)
  • Daily backups: automatic on Pro, verified in Database → Backups
  • PITR: skipped. Add-on is $100–400/mo plus a forced Small-compute upgrade (~$15/mo). Daily backups cover VERA's recovery needs at this scale; PITR revisited if/when a real recovery scenario proves the cost.
  • Railway env var DATABASE_URL_PROD set to the Session Pooler connection string
  • psql smoke test returns PostgreSQL 17.6 on aarch64-unknown-linux-gnu

Gotchas (read before provisioning staging)

Use the Session Pooler connection string, never Direct. Supabase's Direct connection (db.<ref>.supabase.co:5432) is IPv6-only. Consumer ISPs, most Windows boxes, and Railway's outbound network are IPv4. Symptom: Network is unreachable from psql. Fix: Settings → Database → Connection string → Session pooler tab. Host becomes aws-N-<region>.pooler.supabase.com.

Username on Session Pooler requires the project-ref suffix. Format is postgres.<project-ref>, not postgres. Supavisor uses the suffix to route to the right DB; without it, auth falls back to a generic postgres user and fails. Symptom: password authentication failed for user "postgres" (note the missing suffix in the error — that's the diagnostic).

Make the database password URL-safe before anyone else sees the connection string. Supabase's generator uses %, #, !, @, etc. These collide with URL percent-encoding (%) and fragment parsing (#) when the password appears in a postgresql:// URL, and with bash history expansion (!) on the command line. When resetting the password, type your own: 24+ chars of a-zA-Z0-9 plus -_ only.

Don't skip region-matching. Supabase project region must match Railway's app region. Cross-region adds ~80ms per DAL call and compounds hard at morning-briefing time when 15+ sheet reads happen in sequence. VERA is us-east-1 on both sides.

Backups on Pro are automatic and non-optional. Database → Backups shows daily physical backups starting from provision time. No switch to flip.

Local smoke-test recipe (for staging later)

bash
psql 'postgresql://postgres.<project-ref>:<password>@aws-N-<region>.pooler.supabase.com:5432/postgres' \
  -c "select version();"

Single quotes around the URL prevent bash from touching any special chars in the password. Works from any IPv4-only machine including Railway.

Railway wiring

  • Variables → New Variable
  • DATABASE_URL_PROD = the full Session Pooler URL with the real password
  • Don't set USE_POSTGRES — Phase 2 flips that per-tenant during cutover
  • Railway redeploys after the variable is saved. No code reads it yet, so the redeploy is a no-op.

Rollback

None needed — no app code references these env vars yet. Safe to leave.

Next

Phase 1.2 — add pg, pg-pool, node-pg-migrate to package.json. Local dev only; no Supabase connection needed until Phase 1.3 runs the first migration.