← Writing

Branch-aware Postgres migrations, finally

2025-02-28 · 6 min

Database migrations are easy until you have five engineers shipping features on branches that each touch the schema. Suddenly a deploy preview breaks because a column already exists, or a foreign key points at a table that only exists in someone else's branch.

The fix is to make migrations idempotent and branch-scoped. I now write additive migrations by default: new columns are nullable or have sensible defaults, and destructive changes happen in a separate cleanup step after the code has been deployed everywhere.

For deploy previews, I use a short-lived database per branch. The migration runner checks the current schema version before applying anything, so re-running the same migration on a fresh database is harmless.

The result is a workflow where schema changes feel as safe as code changes. Pull requests get their own isolated database, and production migrations stay small, additive, and reversible.