After spending countless hours managing Supabase deployments manually, I've finally set up a proper CI/CD pipeline that makes local development and deployments a breeze. Here's my complete setup for anyone looking to do the same.
Local Development Setup
First things first, you'll need to get your local Supabase instance running. I use Docker for this:
supabase init
supabase start
This spins up your local Supabase instance with all the services you need. The cool part is that it creates a local database that mirrors your production setup.
Branch Management
Supabase's new branching feature is a game-changer. Instead of working directly on production, I create a new branch for each feature:
supabase db branch create feature-user-auth
supabase db branch switch feature-user-auth
Now I can make schema changes locally without affecting production. When I'm ready to test, I push my changes:
supabase db push
Setting Up GitHub Actions
Here's where the magic happens. I've set up a GitHub Actions workflow that automatically handles deployments. Create .github/workflows/supabase-deploy.yml
:
name: Deploy Supabase
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
env:
SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
SUPABASE_DB_PASSWORD: ${{ secrets.SUPABASE_DB_PASSWORD }}
PROJECT_ID: your-project-id
steps:
- uses: actions/checkout@v3
- name: Setup Supabase CLI
uses: supabase/setup-cli@v1
with:
version: latest
- name: Link to Supabase project
run: supabase link --project-ref $PROJECT_ID
- name: Deploy migrations
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
run: supabase db push
- name: Deploy branch preview
if: github.event_name == 'pull_request'
run: |
BRANCH_NAME=$(echo $GITHUB_HEAD_REF | sed 's/[^a-zA-Z0-9]/-/g')
supabase db branch create $BRANCH_NAME || true
supabase db branch switch $BRANCH_NAME
supabase db push
Database Migrations
One thing I learned the hard way: always use migrations. I keep them in the supabase/migrations
directory:
-- 20240119000000_create_users.sql
create table public.users (
id uuid default gen_random_uuid() primary key,
email text unique not null,
created_at timestamptz default now()
);
The CI/CD pipeline automatically runs these migrations in order.
Environment Management
I use different environment variables for local development and production. My .env
looks like this:
# Local development
SUPABASE_PROJECT_ID=local
SUPABASE_DB_PASSWORD=your-local-password
# Production
SUPABASE_ACCESS_TOKEN=your-access-token
PRODUCTION_PROJECT_ID=your-project-id
These get mapped to GitHub Secrets for the CI/CD pipeline.
Testing the Pipeline
When I create a PR, the GitHub Action automatically:
- Creates a new Supabase branch
- Deploys my schema changes to that branch
- Runs any migrations
Once the PR is merged to main, it:
- Applies the changes to production
- Runs the migrations in order
- Updates the schema
Tips from the Trenches
- Always backup your production database before major deployments
- Use
supabase db diff
to see what changes you're about to push - Keep your migrations atomic and reversible
- Test your migrations locally first using
supabase db reset
- Use seed data for testing:
supabase db seed
This setup has saved me countless hours and prevented many deployment headaches. The automated pipeline catches issues early, and the branch previews let me test changes safely before they hit production.
Feel free to adapt this to your needs - every project is different, but this foundation should work for most use cases.