Skip to content

Multi-Environment Branching Setup Guide

This guide covers the setup for multi-environment deployment using Supabase preview branches.

Branch Types: Persistent vs Ephemeral

Supabase branches in this project are managed as two types:

Persistent Branches (Never Deleted)

Branch Type Lifecycle Data
main Production Always active Preserved forever
develop Staging Pausable, auto-unpause on push Preserved across pauses

Characteristics: - Created once and reused indefinitely - Database state is preserved - Secrets are set once and persist - Staging is paused (not deleted) when inactive to save resources - Staging is automatically unpaused when code is pushed

Ephemeral Branches (Deleted After Use)

Branch Type Lifecycle Data
pr-* Preview Created per-PR, deleted on close Lost on deletion

Characteristics: - Created when PR is labeled with deploy-preview - Deleted when PR is merged or closed - Database and secrets are destroyed on deletion - Each PR gets isolated testing environment - Auto-generated unique secrets for security isolation

Architecture Overview

Single Supabase Project: $SUPABASE_PROJECT_ID
├── main branch (production) [PERSISTENT - ALWAYS ACTIVE]
│   └── Git: main → Deploy to Supabase main branch
│   └── GitHub environment: production
│   └── Secrets set by: deploy-production.yml
│   └── Never paused, never deleted
├── develop branch (staging) [PERSISTENT - PAUSABLE]
│   └── Git: develop → Deploy to Supabase develop branch
│   └── GitHub environment: staging
│   └── Secrets set by: deploy-staging.yml
│   └── Auto-paused after 24h of inactivity (pause-staging.yml)
│   └── Auto-unpaused on next push to develop
│   └── NEVER deleted - data is preserved
└── pr-* branches (preview) [EPHEMERAL]
    └── Git: PR branches → Create temp Supabase branches
    └── GitHub environment: preview-pr-{number}
    └── Secrets set by: pr-checks.yml
    └── DELETED when PR closed (cleanup-preview.yml)
    └── Data is NOT preserved

Branch Lifecycle Summary

Branch Type Created Paused Deleted
main Persistent Always exists Never Never
develop Persistent On first push After 24h inactivity Never
pr-* Ephemeral On PR with deploy-preview label Never On PR close

Prerequisites

  • Supabase CLI installed (npm i -g supabase)
  • Supabase access token (from Dashboard → Account → Access Tokens)
  • Project ID (from Dashboard → Settings → General → Reference ID)
  • GitHub repository with Actions enabled

One-Time Setup

1. Authenticate with Supabase CLI

supabase login
supabase link --project-ref YOUR_PROJECT_ID

3. Configure GitHub Secrets

Add the required secrets to your GitHub repository (see GitHub Secrets section below).

4. Push to Develop to Create Staging Branch

The staging branch is created automatically on first push to develop:

git checkout -b develop
git push -u origin develop

This triggers deploy-staging.yml which: 1. Creates the develop branch in Supabase (if not exists) 2. Applies migrations 3. Sets branch secrets 4. Deploys Edge Functions

5. Seed Staging Data (Optional)

# Apply staging seed data (after branch is created)
supabase db reset --branch develop

GitHub Repository Setup

Required Secrets

Repository Secrets (Settings → Secrets and variables → Actions → Repository secrets):

Secret Description Where to Find
SUPABASE_ACCESS_TOKEN CLI authentication token Dashboard → Account → Access Tokens
SUPABASE_PROJECT_ID Project reference ID Dashboard → Settings → General
SUPABASE_PUBLISHABLE_KEY Public API key (for smoke tests) Dashboard → Settings → API

Environment Secrets (Settings → Environments → {environment name}):

Environment Secret Description How to Generate
production SERVICE_AUTH_SECRET_PRODUCTION Production service-to-service auth openssl rand -base64 32
staging SERVICE_AUTH_SECRET_STAGING Staging service-to-service auth (also seeds PR secrets) openssl rand -base64 32

Note: PR preview branches auto-generate unique secrets—no manual setup needed.

Understanding Supabase Branching

API Keys are PROJECT-level: - SUPABASE_PUBLISHABLE_KEY is the same for all branches - You only need ONE publishable key in GitHub repository secrets - Used by smoke tests and health checks to verify deployments

Database is BRANCH-level: - Each branch (main, develop, pr-) has its own *isolated database instance** - Migrations are applied per-branch - Data does not leak between branches

Secrets are BRANCH-level: - Application secrets like SERVICE_AUTH_SECRET are set per-branch - CI/CD automatically sets secrets during deployment

Branch-to-Secret Mapping

Branch Type Git Branch Supabase Branch GitHub Environment SERVICE_AUTH_SECRET
Production main main production From SERVICE_AUTH_SECRET_PRODUCTION
Staging develop develop staging From SERVICE_AUTH_SECRET_STAGING
PR Preview feat/*, etc. pr-{number} preview-pr-{number} Auto-generated per PR

How Secrets Are Set (Automatic via CI/CD)

Production (on push to main):

# deploy-production.yml automatically runs:
supabase secrets set \
  ENVIRONMENT=production \
  LOG_LEVEL=info \
  SERVICE_AUTH_SECRET="${{ secrets.SERVICE_AUTH_SECRET_PRODUCTION }}"

Staging (on push to develop):

# deploy-staging.yml automatically runs:
supabase secrets set --branch develop \
  ENVIRONMENT=staging \
  LOG_LEVEL=debug \
  SERVICE_AUTH_SECRET="${{ secrets.SERVICE_AUTH_SECRET_STAGING }}"

Preview (on PR with deploy-preview label):

# pr-checks.yml automatically runs:
# Generates unique secret per PR
supabase secrets set --branch pr-N \
  ENVIRONMENT=preview \
  LOG_LEVEL=debug \
  SERVICE_AUTH_SECRET="<auto-generated>"

GitHub Environments

Create these environments in your repository (Settings → Environments):

  1. production
  2. Protection rules: Require reviewers
  3. Deployment branches: main only

  4. staging

  5. No protection rules needed
  6. Deployment branches: develop only

  7. preview-pr-* (dynamic)

  8. Created automatically by workflows

Git Branch Strategy

Main Branches

  • main - Production code, deploys to Supabase main branch
  • develop - Staging code, deploys to Supabase develop branch

Feature Branches

# Create feature branch from develop
git checkout develop
git checkout -b feat/my-feature

# Work on feature...
git add .
git commit -m "Add my feature"

# Push and create PR to develop
git push -u origin feat/my-feature
# Create PR on GitHub

Preview Deployments

  1. Create a PR to develop or main
  2. Add the deploy-preview label to the PR
  3. A preview branch is automatically created with secrets
  4. Preview is deleted when PR is closed

Deployment Workflows

Automatic Deployments

Trigger Target Workflow Action
Push to main Production deploy-production.yml Deploy + set secrets
Push to develop Staging deploy-staging.yml Create/unpause + deploy
PR with deploy-preview label Preview pr-checks.yml Create + deploy
PR closed PR preview cleanup-preview.yml Delete branch
Daily (midnight UTC) Staging pause-staging.yml Pause if inactive 24h

Branch States

Staging Branch (develop): - Active: Running and serving requests - Paused: Stopped to save resources, data preserved - Automatically unpaused when you push to develop - Manually unpause: supabase branches unpause develop

PR Branches (pr-*): - Created when PR has deploy-preview label - Deleted when PR is merged or closed - Never paused (short-lived)

Manual Deployments

# Deploy to staging
deno task deploy --branch develop

# Deploy to production (main branch)
deno task deploy

# Deploy specific function to staging
deno task deploy graph --branch develop

Health Checks

Verify Deployments

# Check staging health
curl https://YOUR_PROJECT.supabase.co/functions/v1/health/health/live
curl https://YOUR_PROJECT.supabase.co/functions/v1/health/health/ready

# Run smoke tests
deno task smoke:staging
deno task smoke:production

Managing Branch States

Check Branch Status

# List all branches and their status
supabase branches list

# Get detailed info about staging branch
supabase branches get develop --output json

Manually Pause/Unpause Staging

# Pause staging to save resources
supabase branches pause develop

# Unpause staging (or just push to develop)
supabase branches unpause develop

Force Pause via GitHub Actions

# Trigger the pause workflow manually
gh workflow run pause-staging.yml -f force_pause=true

Verifying Secrets

# List production secrets
supabase secrets list

# List staging secrets
supabase secrets list --branch develop

# List preview secrets
supabase secrets list --branch pr-123

Rollback Procedures

Rollback Functions

# List recent deployments
deno task rollback:list

# Rollback all functions to specific commit
deno task rollback abc1234

# Rollback specific function
deno task rollback graph abc1234

# Dry run (preview without applying)
deno task rollback --dry-run abc1234

Database Rollback

Database migrations are forward-only. For rollbacks:

  1. Create a new migration that reverts changes
  2. Push to the affected branch
# Create rollback migration
deno task db:new "rollback_feature_x"

# Edit the migration file to undo changes

# Push to staging
supabase db push --branch develop

Troubleshooting

Staging Branch is Paused

If staging returns errors, it might be paused:

# Check branch status
supabase branches get develop --output json

# Unpause manually
supabase branches unpause develop

# Or push to develop (auto-unpauses)
git commit --allow-empty -m "chore: unpause staging"
git push origin develop

Branch Not Found

# List available branches
supabase branches list

# Create staging branch (push to develop)
git push origin develop

# Or create manually
supabase branches create develop

Secrets Not Applied

Secrets are set automatically by CI/CD. If missing:

# Verify secrets on branch
supabase secrets list --branch develop

# Trigger redeployment
git commit --allow-empty -m "chore: refresh secrets"
git push origin develop

Health Check Failing

# Check if branch is paused first
supabase branches get develop --output json

# If paused, unpause
supabase branches unpause develop

# Check function logs
supabase functions logs health --branch develop

# Verify database connectivity
curl https://YOUR_PROJECT.supabase.co/functions/v1/health/health/ready

Preview Branch Issues

# List all preview branches
supabase branches list

# Manually delete stuck preview branch
supabase branches delete pr-123 --confirm

Staging Won't Unpause

If the staging branch fails to unpause:

# Check for errors
supabase branches get develop --output json

# Wait and retry (branches can take time to unpause)
sleep 60
supabase branches unpause develop

# Check Supabase dashboard for branch health
# Dashboard → Database → Branches → develop

Note: The staging branch is PERSISTENT and should never be deleted. Deleting it will lose all staging data. If you must recreate it, coordinate with the team first.

Best Practices

Branch Management

  1. Never delete persistent branches - main and develop are persistent; only pr-* branches should be deleted
  2. Use ephemeral PR branches for risky changes - They're deleted after PR close, no cleanup needed
  3. Let staging auto-pause - Don't manually pause; let the scheduled workflow handle it

Development Workflow

  1. Always test in staging first - Never deploy directly to production
  2. Keep migrations small - Easier to rollback if needed
  3. Use deploy-preview label sparingly - PR branches consume Supabase resources

Security

  1. Never store remote secrets locally - Let CI/CD handle secret management
  2. Rotate secrets periodically - Update GitHub Secrets quarterly
  3. Review deployment logs - Check GitHub Actions for issues

Monitoring

  1. Monitor health endpoints - Set up alerts for production
  2. Check branch status regularly - Ensure staging isn't stuck paused