Software Engineer at Supabase
January 27, 2026
Supabase gives you:
Local Postgres (supabase start)
SQL migrations in supabase/migrations/
A CLI to apply them and wire into CI
Below are three deployment patterns. Pick based on team size and risk tolerance.
Local development
Local DB via supabase start
Generate migrations with supabase migration new or supabase db diff
Validate by nuking + recreating:
supabase db resetIf reset passes, migrations are valid from zero.
Source of truth
Schema lives in supabase/migrations/ in git
Avoid manual dashboard edits in remote envs
If someone edits remotely, use supabase db pull to bring it back into code
Pattern A: Local → Production (Solo Developer)
Simplest setup. Good when you’re the only person touching the DB.
Edit schema locally
Generate migration
supabase db reset locally
Commit to git
Push to main
CI applies migrations to prod project
flowchart LR
A[Push to main] --> B[CI]
B --> C[Run tests on local DB]
C --> D[Apply migrations to prod]
D --> E[Done]name: Migrations - Solo Local to Prod
on:
push:
branches: [ main ]
jobs:
migrate-prod:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: supabase/setup-cli@v1
with:
version: latest
# Optional: sanity-check migrations on a local DB
- name: Validate migrations locally
run: |
supabase db start
supabase db reset
- name: Push migrations to prod
env:
SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
SUPABASE_DB_PASSWORD: ${{ secrets.PROD_DB_PASSWORD }}
SUPABASE_PROJECT_ID: ${{ secrets.PROD_PROJECT_ID }}
run: supabase db push --non-interactive --linkedSUPABASE_ACCESS_TOKEN, SUPABASE_DB_PASSWORD, SUPABASE_PROJECT_ID are exactly what Supabase recommends for non-interactive CLI in CI.
Fast and simple
Good for solo side projects
Easy mental model
No shared staging env
Mistakes go straight to prod
Pattern B: Local → Remote Branch (Staging) → Prod Branch
(Supabase Branching, Pro tier)
Here you use Supabase Database Branching:
One Supabase project
Multiple DB branches: e.g. staging and production
Each branch has its own DB URL / credentials
Good for teams that want a proper staging env without juggling multiple projects.
Work locally on a feature branch
Generate migrations and test locally
Push to Git develop → CI applies to staging branch
After sign-off, merge to main → CI applies to production branch
flowchart LR
subgraph Staging
A[Push to develop] --> B[CI]
B --> C[Apply to Supabase DB branch: staging]
end
subgraph Production
D[Merge to main] --> E[CI]
E --> F[Apply to Supabase DB branch: production]
endAssuming:
Git branch develop → Supabase DB branch staging
Git branch main → Supabase DB branch production
You store branch DB URLs as secrets
name: Migrations - Branching (Staging & Prod)
on:
push:
branches: [ develop, main ]
jobs:
migrate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: supabase/setup-cli@v1
with:
version: latest
- name: Validate migrations locally
run: |
supabase db start
supabase db reset
- name: Select target DB URL
id: target
run: |
if [[ "${GITHUB_REF_NAME}" == "develop" ]]; then
echo "db_url=${{ secrets.SUPABASE_STAGING_DB_URL }}" >> "$GITHUB_OUTPUT"
else
echo "db_url=${{ secrets.SUPABASE_PROD_DB_URL }}" >> "$GITHUB_OUTPUT"
fi
- name: Push migrations to branch
env:
SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
run: |
supabase db push \
--db-url "${{ steps.target.outputs.db_url }}" \
--non-interactiveHere we use --db-url to point the CLI directly at the branch DB connection string, which Supabase exposes per branch.
Proper isolated staging env
No extra projects
Teams can test against a realistic DB
Pro feature
You now manage multiple DB URLs
Still need discipline to avoid “hotfix in prod branch without migration”
Pattern C: Local → Free Staging Project → Prod Project
This one uses two remote projects:
Project 1: staging (free)
Project 2: production (free or paid)
Good for teams on free tier willing to manage two Supabase projects.
Local changes
Generate migrations
Push to develop → CI applies to staging project
Test & QA
Merge to main → CI applies same migrations to prod project
flowchart LR
subgraph Staging project
A[Push to develop] --> B[CI]
B --> C[Apply migrations to free staging project]
end
subgraph Prod project
D[Merge to main] --> E[CI]
E --> F[Apply migrations to prod project]
endIf people edit schemas directly in:
Staging project dashboard
Prod project dashboard
you will eventually have two different schemas and one sad developer.
Fix it by making code the source of truth:
Only change DB through migrations
supabase db diff to capture manual changes into migrations
If a project is totally cursed, recreate it from migrations
name: Migrations - Dual Projects (Staging & Prod)
on:
push:
branches: [ develop, main ]
jobs:
migrate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: supabase/setup-cli@v1
with:
version: latest
- name: Validate migrations locally
run: |
supabase db start
supabase db reset
- name: Set Supabase env for staging or prod
id: target
run: |
if [[ "${GITHUB_REF_NAME}" == "develop" ]]; then
echo "project_id=${{ secrets.STAGING_PROJECT_ID }}" >> "$GITHUB_OUTPUT"
echo "db_password=${{ secrets.STAGING_DB_PASSWORD }}" >> "$GITHUB_OUTPUT"
else
echo "project_id=${{ secrets.PROD_PROJECT_ID }}" >> "$GITHUB_OUTPUT"
echo "db_password=${{ secrets.PROD_DB_PASSWORD }}" >> "$GITHUB_OUTPUT"
fi
- name: Push migrations to remote
env:
SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
SUPABASE_PROJECT_ID: ${{ steps.target.outputs.project_id }}
SUPABASE_DB_PASSWORD: ${{ steps.target.outputs.db_password }}
run: supabase db push --non-interactive --linkedThis follows Supabase’s recommended CI env var setup, just parametrized per branch.
Real staging env separate from prod
Works on free org with 2 free projects
Clear blast radius: staging vs prod
Schema drift if people click in dashboards
More secrets and config to manage
Slightly more complex CI
PatternEnvsSupabase featuresTeam sizeSafetyA Local→Prod1 prod projectBaseSoloLowB Local→Branch→Prod1 project, multiple DB branchesBranching (Pro)TeamMedium–HighC Local→Free→Prod2 projectsBaseTeamHigh (with discipline)
There’s a whole other world where:
You keep a single declarative schema file
A tool computes diffs and generates migrations
CI checks remote DB drift against the desired schema
That’s a good topic for its own post. It plays nicely with all three patterns above, but changes how you author changes.