Managed Postgres recommendations, sizing, backups, and prod migration workflow.

Production Database

The local embedded database (PGlite) is perfect for development and single-player authoring in the Studio, but it is not suitable for a multiplayer production server. This page covers the managed-Postgres options, sizing by player count, and the production migration workflow.

Why Not PGlite in Production

PGlite is a single-process WASM build of Postgres. That means:

  • It runs in-process with the game server — you cannot share it with a load-balanced admin panel or a second game server.
  • On-disk corruption from a mid-write crash has no replication safety net (you'll see pglite-server.corrupt-* folders in data/ during dev — those are automatic quarantine dumps).
  • No managed backups, no point-in-time recovery, no connection pooling across processes.
For production, switch to a managed Postgres provider and set DB_MODE=remote with DATABASE_URL in your .env.

Managed Postgres Providers

ProviderGood ForNotes
SupabaseTeams already using Supabase authBuilt-in PgBouncer, row-level security, nice dashboard
NeonServerless / variable loadAuto-suspend on idle, branching for staging/prod
RailwayFastest setup, small teamsSimple pricing, one-click Postgres
Amazon RDSLarge-scale productionFull control, VPC networking, read replicas
Fly.io PostgresCo-located with game server on FlyLow latency when the server is also on Fly

Sizing by Concurrent Players

These are starting points — measure your tick-duration histogram and scale up before you hit overruns.

Concurrent PlayersvCPURAMStorage
10024 GB20 GB SSD
50048 GB50 GB SSD
2,000816 GB100 GB SSD + PITR
2,000+16+32+ GB250+ GB NVMe + read replica + PITR

Connection String

Use the transaction-mode pooler URL (PgBouncer) in DATABASE_URL, not the direct-connection URL. This keeps the per-connection footprint low when the game server opens many short-lived queries during a tick.

DB_MODE=remote
DATABASE_URL="postgres://user:pass@pooler.example.com:6543/ed5?pgbouncer=true&connection_limit=20"

Required Extensions

The schema uses a couple of standard extensions. Enable them before running migrations:

CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pg_trgm";

Backups

  • Take a daily pg_dump -Fc snapshot off the database host (another VM, S3, or the provider's built-in backup).
  • Test a restore at least once per quarter. An untested backup is not a backup.
  • Enable point-in-time recovery (PITR) for 2,000+ concurrent players.

Production Migration Workflow

Never run pnpm db:push against a production database. Use migrations:

1. Snapshot the database before every deploy. 2. Generate migrations locally from schema changes:

cd packages/db && pnpm build && npx drizzle-kit generate

3. Review the generated SQL in packages/db/drizzle/ and commit it. 4. Apply with drizzle-kit migrate (or your CI/CD deployment step). 5. Verify schema version and smoke-test a small number of reads/writes before opening the flood gates.

See DEPLOYMENT.md in the repo root for the exact commands and the full production checklist.