Streamlining Supabase Development with Branch Previews and GitHub Actions

2024-07-11

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:

  1. Creates a new Supabase branch
  2. Deploys my schema changes to that branch
  3. Runs any migrations

Once the PR is merged to main, it:

  1. Applies the changes to production
  2. Runs the migrations in order
  3. 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.