We run Supabase Branching (Pro plan) with two long-lived environments:
| Env | Git branch | Supabase branch | Project ref |
|---|---|---|---|
| Production | main | main (default) | pstvpdcqmsodirmrvzam |
| Development | develop | develop (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:
- Our baseline broke replay on non-empty schemas. The
item_blackouts → unitsFK block was the oneunitsreference not guarded onunitsexisting; 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 in0001_baseline.sql. - Nothing was git-linked. The persistent dev branch had no
git_branchbinding (zero integration runs after creation), and automatic branching wasn't enabled. Brandon enabled preview branches (which linkedmain); the dev branch was PATCHed togit_branch: developvia 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 start —
docker 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_migrationdirectly 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.