FastAPI

FastAPI Alembic Migrations — Database Version Control

Thirdy Gayares
13 min read

🎓 What You Will Learn

  • Alembic Setup: Initialize migrations for your project
  • Creating Migrations: Auto-generate and write custom migrations
  • Upgrading/Downgrading: Move between schema versions
  • Data Migrations: Transform data while changing schema
  • Production Deployment: Safe migration strategies
  • Troubleshooting: Handle migration conflicts and rollbacks
AlembicSQLAlchemyPostgreSQL

1Why Database Migrations Matter

Without migrations, your database schema becomes untracked and unpredictable. Migrations provide version control for your database, allowing safe schema changes across development, staging, and production environments.

Best Practice: Every schema change should have a corresponding migration file, tracked in version control.

2Setting Up Alembic

Initialize Alembic in your FastAPI project.

bash
pip install alembic sqlalchemy
alembic init migrations
migrations/env.py
from app.database import Base
from app.models import *

target_metadata = Base.metadata

📋 Migration Timeline

001_initial_schema
applied2026-01-15
002_add_user_email
applied2026-01-20
003_create_posts_table
applied2026-02-01
004_add_post_indexes
current2026-02-21
✅ Track every schema change with version control!

3Creating Your First Migration

Auto-generate migrations from your SQLAlchemy models.

bash
alembic revision --autogenerate -m "Create user table"

4Understanding Migration Files

Migration files contain the schema changes and are tracked in version control.

migrations/versions/001_create_user_table.py
from alembic import op
import sqlalchemy as sa

revision = "001"
down_revision = None

def upgrade():
    op.create_table(
        "user",
        sa.Column("id", sa.Integer(), nullable=False),
        sa.Column("email", sa.String(), nullable=False),
        sa.Column("name", sa.String()),
        sa.PrimaryKeyConstraint("id"),
        sa.UniqueConstraint("email")
    )

def downgrade():
    op.drop_table("user")

5Applying and Rolling Back Migrations

Use Alembic commands to move between schema versions.

bash
# Apply migrations
alembic upgrade head

# Upgrade to specific revision
alembic upgrade 001

# Rollback one migration
alembic downgrade -1

# Rollback to base
alembic downgrade base

# View migration history
alembic current
alembic history

6Data Migrations

Transform data while changing schema.

migrations/versions/002_add_user_phone.py
from alembic import op
import sqlalchemy as sa

def upgrade():
    # Add new column
    op.add_column("user", sa.Column("phone", sa.String()))

    # Migrate data
    connection = op.get_bind()
    connection.execute(sa.text("UPDATE user SET phone = '000-0000' WHERE phone IS NULL"))

def downgrade():
    op.drop_column("user", "phone")

7Adding Constraints and Indexes

Optimize performance with indexes and enforce data integrity with constraints.

migrations/versions/003_add_indexes.py
from alembic import op

def upgrade():
    op.create_index("idx_user_email", "user", ["email"])
    op.create_check_constraint("ck_user_age", "user", "age >= 18")

def downgrade():
    op.drop_index("idx_user_email")
    op.drop_constraint("ck_user_age", "user")

8Writing Custom Migrations

For complex logic, write migrations manually.

bash
# Create empty migration
alembic revision -m "Custom logic"

9Environment-Specific Migrations

Different migrations for dev, staging, and production.

bash
# Development
alembic upgrade head

# Production (dry run first)
alembic upgrade head --sql > migrations.sql
# Review migrations.sql
alembic upgrade head

Safety First: Always backup production before running migrations. Test migrations on a replica first.

10Handling Migration Conflicts

When multiple branches create migrations, resolve conflicts by renaming revisions.

bash
# If two branches create 002_migration.py, rename one:
mv migrations/versions/002_add_field_a.py \
   migrations/versions/003_add_field_a.py

# Update revision and down_revision fields in the file

11Testing Migrations

Test migrations automatically.

tests/test_migrations.py
import subprocess

def test_migrations_apply():
    # Run migrations on test database
    result = subprocess.run(
        ["alembic", "upgrade", "head"],
        capture_output=True
    )
    assert result.returncode == 0

def test_migrations_rollback():
    result = subprocess.run(
        ["alembic", "downgrade", "base"],
        capture_output=True
    )
    assert result.returncode == 0

12Safe Production Migration Strategy

Best practices for production migrations.

  • Backup first: Always backup production database before migrating
  • Test on replica: Run migrations on a replica of production
  • Downtime window: Schedule migrations during maintenance window
  • Rollback plan: Know how to rollback quickly if needed
  • Monitor: Watch application behavior after migration

Blue-Green Deployment: Run two identical production environments, migrate on blue, then switch traffic to blue.

13Common Migration Pitfalls

  • Missing migrations: Schema changes without migrations create inconsistency
  • Large batches: Don't migrate too many changes at once
  • Non-idempotent migrations: Migrations should be safe to run twice
  • No downgrade path: Always write downgrade logic
  • Locking tables: Long-running migrations can lock tables

14Advanced Topics

  • Zero-downtime migrations: Add columns, then migrate, then remove old column
  • Parallel migrations: Run multiple migrations across shards
  • Squashing migrations: Combine old migrations into one
  • Conditional migrations: Run migrations based on environment

15Resources & What's Next

You now understand how to manage database schema changes safely. Use migrations for every change and track them in version control.

Next Topics: Security and authentication, deployment strategies, and monitoring.

Congratulations! Your database is now under version control. Deploy with confidence! 🚀

About the Author

TG

Thirdy Gayares

Passionate developer creating custom solutions for everyone. I specialize in building user-friendly tools that solve real-world problems while maintaining the highest standards of security and privacy.