Supabase Migrations & Rollback
Tại sao cần Migrations?
Vấn đề khi không có migrations
❌ Direct changes via Dashboard
- Không track được history
- Không sync được giữa environments
- Không rollback được
❌ Manual SQL execution
- Dễ quên bước
- Không consistent
- Khó reproduce
Migrations giải quyết
✅ Version controlled schema
✅ Consistent across environments
✅ Reviewable in PRs
✅ Rollback capable
✅ Automated deployment
Migration Workflow
┌─────────────────────────────────────────────────────────────┐
│ MIGRATION WORKFLOW │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. LOCAL DEVELOPMENT │
│ ┌──────────────────────────────────────────────┐ │
│ │ supabase migration new <name> │ │
│ │ Edit migration SQL │ │
│ │ supabase db reset (test locally) │ │
│ └──────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 2. CODE REVIEW │
│ ┌──────────────────────────────────────────────┐ │
│ │ git commit -m "Add users table" │ │
│ │ Create Pull Request │ │
│ │ Review migration SQL │ │
│ └──────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 3. DEPLOY TO STAGING │
│ ┌──────────────────────────────────────────────┐ │
│ │ supabase db push --linked (staging) │ │
│ │ Test in staging environment │ │
│ └──────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 4. DEPLOY TO PRODUCTION │
│ ┌──────────────────────────────────────────────┐ │
│ │ supabase db push --linked (production) │ │
│ │ Monitor for issues │ │
│ └──────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
Creating Migrations
Tạo migration mới
# Create new migration
supabase migration new add_tasks_table
# Creates: supabase/migrations/20240115120000_add_tasks_table.sql
Migration file structure
-- supabase/migrations/20240115120000_add_tasks_table.sql
-- UP Migration (applied)
CREATE TABLE public.tasks (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
title TEXT NOT NULL,
description TEXT,
status TEXT DEFAULT 'pending',
user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Enable RLS
ALTER TABLE public.tasks ENABLE ROW LEVEL SECURITY;
-- Create policies
CREATE POLICY "Users can manage own tasks"
ON public.tasks FOR ALL
USING (auth.uid() = user_id);
-- Create index
CREATE INDEX idx_tasks_user_id ON public.tasks(user_id);
CREATE INDEX idx_tasks_status ON public.tasks(status);
-- Trigger for updated_at
CREATE TRIGGER set_updated_at
BEFORE UPDATE ON public.tasks
FOR EACH ROW
EXECUTE FUNCTION extensions.moddatetime(updated_at);
Testing Migrations Locally
# Reset database and apply all migrations
supabase db reset
# This will:
# 1. Drop all tables
# 2. Apply all migrations in order
# 3. Run seed.sql
# Verify migration
supabase db diff
# Generate types after migration
supabase gen types typescript --local > types/supabase.ts
CI/CD Integration
GitHub Action for Migrations
# .github/workflows/migrations.yml
name: Database Migrations
on:
push:
branches: [staging, main]
paths:
- 'supabase/migrations/**'
jobs:
migrate-staging:
if: github.ref == 'refs/heads/staging'
runs-on: ubuntu-latest
environment: staging
steps:
- uses: actions/checkout@v4
- name: Setup Supabase CLI
uses: supabase/setup-cli@v1
with:
version: latest
- name: Link to Staging
run: |
supabase link --project-ref ${{ vars.SUPABASE_PROJECT_REF }}
env:
SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
- name: Run Migrations
run: supabase db push
env:
SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
- name: Verify Migration
run: supabase db diff
env:
SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
migrate-production:
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- name: Setup Supabase CLI
uses: supabase/setup-cli@v1
- name: Link to Production
run: supabase link --project-ref ${{ vars.SUPABASE_PROJECT_REF }}
env:
SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
- name: Run Migrations
run: supabase db push
env:
SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
Multi-environment Setup
Environment-specific configs
# Staging
supabase link --project-ref abc123staging
# Production
supabase link --project-ref xyz789production
Switching between projects
# Create config for each environment
# supabase/.env.staging
SUPABASE_PROJECT_REF=abc123staging
# supabase/.env.production
SUPABASE_PROJECT_REF=xyz789production
# Use specific environment
supabase db push --linked --project-ref $SUPABASE_PROJECT_REF
Rollback Strategies
Strategy 1: Reverse Migration
-- supabase/migrations/20240115130000_rollback_tasks_table.sql
-- Rollback: Drop table and related objects
DROP TRIGGER IF EXISTS set_updated_at ON public.tasks;
DROP INDEX IF EXISTS idx_tasks_user_id;
DROP INDEX IF EXISTS idx_tasks_status;
DROP POLICY IF EXISTS "Users can manage own tasks" ON public.tasks;
DROP TABLE IF EXISTS public.tasks;
# Apply rollback migration
supabase migration new rollback_tasks_table
# Edit the file with DROP statements
supabase db push
Strategy 2: Data-preserving Rollback
-- supabase/migrations/20240115130000_rollback_preserve_data.sql
-- 1. Backup data to temp table
CREATE TABLE public.tasks_backup AS SELECT * FROM public.tasks;
-- 2. Drop new table
DROP TABLE public.tasks;
-- 3. Restore to previous schema
CREATE TABLE public.tasks (
id UUID PRIMARY KEY,
title TEXT NOT NULL,
-- Previous schema without new columns
);
-- 4. Restore data
INSERT INTO public.tasks SELECT id, title FROM public.tasks_backup;
-- 5. Clean up
DROP TABLE public.tasks_backup;
Rollback via Dashboard
Point-in-time Recovery (Paid plans)
1. Supabase Dashboard → Database → Backups
2. Select backup point
3. Restore to new project or existing
Limitations:
- Pro plan: 7 days retention
- Only available on paid plans
Manual Backup & Restore
# Backup before risky migration
pg_dump -h db.xxx.supabase.co -U postgres -d postgres > backup.sql
# Restore if needed
psql -h db.xxx.supabase.co -U postgres -d postgres < backup.sql
Safe Migration Practices
1. Additive Migrations First
-- ✅ SAFE: Add nullable column
ALTER TABLE users ADD COLUMN phone TEXT;
-- ❌ RISKY: Add non-null column without default
ALTER TABLE users ADD COLUMN phone TEXT NOT NULL;
-- ✅ SAFE: Add with default
ALTER TABLE users ADD COLUMN phone TEXT NOT NULL DEFAULT '';
2. Two-phase Column Removal
-- Phase 1: Deprecate (stop using in code)
-- Update code to not use 'old_column'
-- Phase 2: Remove (after code deployed)
ALTER TABLE users DROP COLUMN old_column;
3. Zero-downtime Renames
-- Step 1: Add new column
ALTER TABLE users ADD COLUMN full_name TEXT;
-- Step 2: Copy data
UPDATE users SET full_name = name;
-- Step 3: Update code to use full_name
-- Step 4: Drop old column
ALTER TABLE users DROP COLUMN name;
Migration Checklist
Before Migration
- [ ] Test locally with
supabase db reset
- [ ] Review SQL for destructive operations
- [ ] Check RLS policies
- [ ] Backup production (if major change)
After Migration
- [ ] Verify with
supabase db diff
- [ ] Generate new types
- [ ] Test application
- [ ] Monitor error rates
Rollback Plan
- [ ] Prepare rollback migration
- [ ] Document rollback steps
- [ ] Test rollback locally
Debugging Migration Issues
Check migration status
# List applied migrations
supabase migration list
# Check differences
supabase db diff
# View pending migrations
supabase db diff --use-migra
Common Errors
| Error |
Cause |
Solution |
relation already exists |
Table exists |
Use IF NOT EXISTS |
column cannot be cast |
Type mismatch |
Create new column, migrate data |
violates foreign key |
Invalid reference |
Fix data first |
permission denied |
RLS blocking |
Use service role |
Tổng kết
Migration Commands
# Create migration
supabase migration new <name>
# Apply locally
supabase db reset
# Push to remote
supabase db push
# Check diff
supabase db diff
# List migrations
supabase migration list
Best Practices
- Small, focused migrations - One concern per file
- Test locally first -
supabase db reset
- Review in PR - SQL is code
- Additive first - Add before remove
- Always have rollback plan - Prepare reverse migration
Q&A
- Có kinh nghiệm với database migrations chưa?
- Đã từng cần rollback migration chưa?
- Challenges với schema changes?