Docs.

Migrations & DB environments

architecture/migrations.md

We run Supabase Branching (Pro plan) with two long-lived environments:

EnvGit branchSupabase branchProject ref
Productionmainmain (default)pstvpdcqmsodirmrvzam
Developmentdevelopdevelop (persistent)hjymyeqttajknzjcombd

(The dev branch was recreated 2026-06-08 — the old nrjilrwjbahxrxallkyi ref is gone, and the new branch's git-binding is blank, so develop pushes don't auto-deploy to it.)

The Vercel-Supabase integration auto-syncs the right Supabase env vars to each Vercel deployment: production deploys (main) point at the prod DB, preview deploys (develop) point at the dev DB.

Migration workflow

Forward-only. The repo's supabase/migrations/ is the canonical source of truth. Supabase Branching reads from the repo when a branch is created/reset.

1. Create a new migration file locally
   supabase migration new <name>
   (or hand-write `supabase/migrations/00NN_<name>.sql`)

2. Apply + verify locally
   supabase db reset --local          # replays all migrations against local
   # or, faster:
   supabase db push --local           # applies only pending migrations

3. Commit + push to develop
   git add supabase/migrations/00NN_*.sql
   git commit -m "..."
   git push github develop
   # The Branching integration applies pending migrations to the
   # persistent dev branch on every develop push (git-linked
   # 2026-06-12) — check the branch's View Logs if in doubt.

4. Verify on the preview deploy
   The Vercel preview build hits the develop DB; click around and
   confirm.

5. Bump version + update CHANGELOG before opening the PR
   - Edit package.json: bump `version` (semver — patch for fixes,
     minor for features, major for breaking changes).
   - Edit CHANGELOG.md: move entries from `[Unreleased]` to a new
     `[X.Y.Z] — YYYY-MM-DD` section.
   - `pnpm release:check` verifies both locally.

6. Merge develop → main
   gh pr create --base main --head develop
   # The release-gate CI check fails the PR if step 5 was skipped.
   # Vercel deploys to production on merge, and the Branching
   # integration applies pending migrations to prod on the same push.

Branching history (repaired 2026-06-12)

For its first four releases the integration applied migrations nowhere — releases 0.9.0–0.12.0 were supabase db pushed by hand. Two separate faults, both fixed 2026-06-12:

  1. Our baseline broke replay on non-empty schemas. The item_blackouts → units FK block was the one units reference not guarded on units existing; the integration's branch-creation replay died on it (relation "public.units" does not exist, recovered from the branch postgres logs). Local from-empty replays could never reproduce it. Guard added in 0001_baseline.sql.
  2. Nothing was git-linked. The persistent dev branch had no git_branch binding (zero integration runs after creation), and automatic branching wasn't enabled. Brandon enabled preview branches (which linked main); the dev branch was PATCHed to git_branch: develop via the Management API.

Manual fallback (if the integration ever misbehaves): session-pooler DSNs — port 5432, aws-1-us-east-1.pooler.supabase.com, the branch db_pass comes from GET /v1/branches/{id}; repo secrets SUPABASE_DEV_DB_URL / SUPABASE_PROD_DB_URL hold ready-made copies. supabase db push --db-url "$DSN" is idempotent.

Baseline

supabase/migrations/0001_baseline.sql is a schema-only dump of prod's public schema taken on 2026-05-31. It captures every table, type, function, trigger, RLS-enable, sequence, and grant; the last block adds the ensure_rls event trigger (DB-level, so supabase db dump --schema public doesn't include it on its own).

Before the squash there were 15 migration files with significant drift from prod's supabase_migrations.schema_migrations ledger — 5 files with no ledger entry and 10 ledger entries with no file. Pre-launch, that history wasn't load-bearing; collapsing it into one replayable baseline lets Branching work.

Local stack

Local Supabase runs on Docker via the CLI. Same Postgres major version as remote (currently 17, set in supabase/config.toml's [db].major_version).

supabase start         # boots Postgres + Auth + Storage + Studio
supabase status        # check URLs/keys
supabase db reset      # rebuild local DB from migrations + seed.sql
supabase stop          # tear down (keep volumes)
supabase stop --no-backup  # tear down + drop volumes

If another Supabase project is occupying the default ports (54321-54329), stop those containers before supabase startdocker stop $(docker ps -q --filter name=supabase_*_<other-project>).

Backups

  • Daily automated by Supabase Pro (7-day retention). Restore via dashboard → Database → Backups.
  • Storage bucket object bytes follow a separate retention policy — verify in the dashboard.
  • For belt-and-suspenders: supabase db dump --linked --schema public --data-only -f <path> periodically to a durable location.
  • PITR (point-in-time recovery) is a separate paid add-on; not enabled.

Anti-patterns

  • Don't apply_migration directly to prod ahead of a deploy. The whole point of Branching is to verify schema changes on the dev DB first. Apply locally → push develop → verify → merge to main.
  • Don't run SQL via the Supabase dashboard's SQL editor for schema changes. Dashboard edits don't write to schema_migrations, which is how we got the pre-baseline drift. Always go through a migration file in the repo.
  • Don't push directly to main. Schema lands on prod only via PR merges from develop.