Home Python Alembic: Database Migration Management for SQLAlchemy
Intermediate 3 min · July 14, 2026

Alembic: Database Migration Management for SQLAlchemy

Master Alembic for SQLAlchemy migrations: learn setup, auto-generation, branching, and production debugging with real-world incident and cheat sheet..

N
Naren Founder & Principal Engineer

20+ years shipping production Python across data and backend systems. Written from production experience, not tutorials.

Follow
Production
production tested
July 15, 2026
last updated
2,398
articles · all by Naren
Before you start⏱ 15-20 min read
  • Basic knowledge of Python and SQLAlchemy (see our SQLAlchemy basics tutorial).
  • A working Python environment with pip.
  • A database (PostgreSQL, MySQL, SQLite) for testing.
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer
  • Alembic is a lightweight database migration tool for SQLAlchemy.
  • It tracks schema changes as versioned migration scripts.
  • Use alembic revision --autogenerate to auto-detect model changes.
  • Apply migrations with alembic upgrade head.
  • Rollback with alembic downgrade -1.
✦ Definition~90s read
What is Alembic?

Alembic is a lightweight database migration tool for SQLAlchemy that lets you version-control your database schema changes as Python scripts.

Think of Alembic as a version control system for your database schema, like Git for your code.
Plain-English First

Think of Alembic as a version control system for your database schema, like Git for your code. It records every change you make to your database tables (adding columns, renaming tables) so you can move forward or backward in time, and your team can apply the same changes consistently.

When your application grows, your database schema evolves. You add columns, rename tables, or change constraints. Without a migration tool, you'd manually run SQL scripts, risking inconsistencies across environments. Alembic, created by the author of SQLAlchemy, solves this by providing a version-controlled, repeatable way to manage schema changes.

Alembic generates migration scripts that describe how to transform your database from one version to the next. It integrates tightly with SQLAlchemy models, auto-detecting changes and producing the necessary SQL. This tutorial will guide you from setup to production debugging, including a real-world incident that taught a team the hard way about branching migrations.

By the end, you'll be able to confidently manage database migrations in any Python project, whether it's a Flask API, Django app (with SQLAlchemy), or a FastAPI service.

Setting Up Alembic

First, install Alembic and SQLAlchemy. Then initialize Alembic in your project directory. This creates a migrations folder with an env.py file that configures the migration environment. You need to point Alembic to your SQLAlchemy database URL and metadata. Here's a typical setup:

``bash pip install alembic sqlalchemy alembic init migrations ``

``ini sqlalchemy.url = postgresql://user:pass@localhost/mydb ``

In migrations/env.py, import your SQLAlchemy models and set target_metadata:

``python from myapp.models import Base target_metadata = Base.metadata ``

Now you can create your first migration: alembic revision --autogenerate -m "initial". This scans your models and generates a script with upgrade() and downgrade() functions.

setup_example.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# migrations/env.py
from alembic import context
from sqlalchemy import engine_from_config
from myapp.models import Base  # your SQLAlchemy models

target_metadata = Base.metadata

def run_migrations_online():
    connectable = engine_from_config(
        config.get_section(config.config_ini_section),
        prefix='sqlalchemy.')
    with connectable.connect() as connection:
        context.configure(connection=connection,
                          target_metadata=target_metadata)
        with context.begin_transaction():
            context.run_migrations()
💡Always use --autogenerate
📊 Production Insight
In production, never run alembic upgrade head without first taking a database backup. Use a read-only replica to test migrations.
🎯 Key Takeaway
Initialize Alembic with alembic init, configure your database URL and metadata, then use --autogenerate to create migrations.

Understanding Migration Scripts

Each migration script is a Python file with upgrade() and downgrade() functions. These functions use Alembic's Operations API to modify the schema. Common operations include create_table, add_column, alter_column, and create_index. Here's an example of a migration that adds a profile_pic column:

```python """add profile_pic column

Revision ID: abc123 Revises: def456 Create Date: 2025-03-15 10:00:00 """ from alembic import op import sqlalchemy as sa

revision = 'abc123' down_revision = 'def456'

def upgrade(): op.add_column('user', sa.Column('profile_pic', sa.String(200), nullable=True))

def downgrade(): op.drop_column('user', 'profile_pic') ```

The down_revision links scripts in a chain. Alembic uses this to know the order. You can also use branch_labels for branching migrations (e.g., for feature branches).

migration_script.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# migrations/versions/abc123_add_profile_pic.py
"""add profile_pic column

Revision ID: abc123
Revises: def456
Create Date: 2025-03-15 10:00:00
"""
from alembic import op
import sqlalchemy as sa

revision = 'abc123'
down_revision = 'def456'
branch_labels = None
depends_on = None

def upgrade():
    op.add_column('user', sa.Column('profile_pic', sa.String(200), nullable=True))

def downgrade():
    op.drop_column('user', 'profile_pic')
🔥Revision IDs
📊 Production Insight
Always test both upgrade and downgrade in a staging environment. A broken downgrade can leave your database in an inconsistent state.
🎯 Key Takeaway
Migration scripts define upgrade() and downgrade() using Alembic's Operations API. They are linked via down_revision.

Autogenerating Migrations

Alembic's --autogenerate compares your SQLAlchemy model metadata with the current database state and generates migration scripts. This is the recommended way to create migrations. However, autogenerate cannot detect everything:

  • Can detect: new tables, columns, column type changes, indexes, unique constraints, foreign keys.
  • Cannot detect: table/column renames, data migrations, changes to server defaults, or constraints like CHECK.

For these, you must manually edit the generated script. Always review autogenerated scripts before applying them. Example:

``bash alembic revision --autogenerate -m "add email column" ``

This will produce a script with the detected changes. If you rename a column, autogenerate will see it as a drop + add, which would lose data. So you must manually change it to a rename operation.

autogenerate_example.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
# After running alembic revision --autogenerate -m "add email"
# The generated script might look like:

def upgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.add_column('user', sa.Column('email', sa.String(), nullable=True))
    # ### end Alembic commands ###

def downgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_column('user', 'email')
    # ### end Alembic commands ###
⚠ Always review autogenerated scripts
📊 Production Insight
In CI, run alembic check to ensure the migration script matches the models. This catches missing migrations early.
🎯 Key Takeaway
Use --autogenerate for speed, but always review and manually fix operations it cannot detect (like renames).

Applying and Rolling Back Migrations

To apply all pending migrations, run alembic upgrade head. To roll back the last migration, use alembic downgrade -1. You can also specify a revision ID: alembic upgrade abc123. Alembic tracks the current revision in the alembic_version table.

It's crucial to test downgrades. For example, if you add a NOT NULL column, the downgrade should handle existing data. You can use batch operations for complex changes:

```python def upgrade(): with op.batch_alter_table('user') as batch_op: batch_op.add_column(sa.Column('age', sa.Integer(), nullable=False, server_default='0'))

def downgrade(): with op.batch_alter_table('user') as batch_op: batch_op.drop_column('age') ```

Batch mode is especially useful for SQLite, which doesn't support ALTER TABLE.

batch_migration.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
"""add age column with default

Revision ID: xyz789
Revises: abc123
"""
from alembic import op
import sqlalchemy as sa

revision = 'xyz789'
down_revision = 'abc123'

def upgrade():
    with op.batch_alter_table('user') as batch_op:
        batch_op.add_column(sa.Column('age', sa.Integer(), nullable=False, server_default='0'))

def downgrade():
    with op.batch_alter_table('user') as batch_op:
        batch_op.drop_column('age')
💡Use batch operations for SQLite
📊 Production Insight
Never run alembic upgrade head on production without a recent backup. Use a maintenance window for long-running migrations.
🎯 Key Takeaway
Apply migrations with upgrade head, rollback with downgrade -1. Use batch operations for complex changes and SQLite compatibility.

Branching and Merging Migrations

When multiple developers work on the same project, they may create migration scripts based on the same parent revision. This creates a branch. Alembic supports branching via branch_labels. To resolve branches, you create a merge migration that combines them.

Example: Two developers both create migrations from revision 'abc'. Developer A creates 'def', developer B creates 'ghi'. The migration history becomes a fork. To merge:

``bash alembic merge -m "merge def and ghi" def ghi ``

This creates a new migration with down_revision pointing to both 'def' and 'ghi'. The merge script's upgrade() should be empty (or contain any needed conflict resolution). The downgrade() is usually a no-op.

Branching is powerful but can lead to complex histories. Use feature branches with caution.

merge_migration.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
"""merge def and ghi

Revision ID: merge123
Revises: def, ghi
Create Date: 2025-03-15 12:00:00
"""
from alembic import op
import sqlalchemy as sa

revision = 'merge123'
down_revision = ('def', 'ghi')  # tuple of parent revisions

def upgrade():
    pass

def downgrade():
    pass
🔥Avoid unnecessary branches
📊 Production Insight
In CI, enforce linear history by requiring developers to rebase their migration on the latest head.
🎯 Key Takeaway
Use alembic merge to combine branches. Merge migrations have multiple parents and empty upgrade/downgrade functions.

Data Migrations and Custom Operations

Sometimes you need to transform existing data during a migration, e.g., populate a new column based on another column. Alembic allows you to execute raw SQL or use SQLAlchemy Core within migration scripts. Example:

```python def upgrade(): op.add_column('user', sa.Column('full_name', sa.String())) op.execute("UPDATE user SET full_name = first_name || ' ' || last_name")

def downgrade(): op.drop_column('user', 'full_name') ```

For complex data migrations, use SQLAlchemy's table() to reflect the table and perform bulk updates. Be careful with large datasets; consider batching.

You can also create custom operations by subclassing MigrateOperation. This is advanced but useful for reusable patterns.

data_migration.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
"""populate full_name from first and last name

Revision ID: data123
Revises: xyz789
"""
from alembic import op
import sqlalchemy as sa

revision = 'data123'
down_revision = 'xyz789'

def upgrade():
    op.add_column('user', sa.Column('full_name', sa.String()))
    # Use SQLAlchemy Core for bulk update
    from sqlalchemy.sql import table, column
    user = table('user', column('full_name'), column('first_name'), column('last_name'))
    op.execute(
        user.update().values(
            full_name=user.c.first_name + ' ' + user.c.last_name
        )
    )

def downgrade():
    op.drop_column('user', 'full_name')
⚠ Data migrations are irreversible
📊 Production Insight
For large tables, run data migrations in batches to avoid long locks. Use op.execute() with LIMIT and OFFSET.
🎯 Key Takeaway
Use op.execute() for data migrations. For complex logic, use SQLAlchemy Core. Always test downgrade data integrity.

Production Best Practices

  1. Version control: Always commit migration scripts. Never generate them on production.
  2. CI checks: Run alembic check to ensure models match the latest migration. Also run alembic upgrade head in CI against a test database.
  3. Idempotency: Write migrations that can be run multiple times safely (use if not column_exists checks).
  4. Backup: Before any migration in production, take a database backup.
  5. Rollback plan: Test downgrade scripts. Have a manual rollback procedure.
  6. Environment-specific: Use different migration branches for different environments if needed, but prefer a linear history.
  7. Locking: Long-running migrations can lock tables. Use --sql to generate SQL and run manually with care.
  8. Monitoring: Log migration output and monitor for errors. Use tools like alembic current in health checks.

``yaml # .github/workflows/migrations.yml jobs: test-migrations: runs-on: ubuntu-latest services: postgres: image: postgres:15 env: POSTGRES_PASSWORD: test options: >- --health-cmd pg_isready --health-interval 10s steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 - run: pip install -r requirements.txt - run: alembic upgrade head env: DATABASE_URL: postgresql://postgres:test@localhost:5432/postgres ``

ci_migration_check.pyPYTHON
1
2
3
4
5
6
7
8
9
# In your CI script, you can also run a Python check:
import subprocess
result = subprocess.run(['alembic', 'check'], capture_output=True, text=True)
if result.returncode != 0:
    print("Migrations are not up-to-date!")
    print(result.stdout)
    exit(1)
else:
    print("Database schema is up-to-date.")
💡Use `alembic check` in CI
📊 Production Insight
Consider using a migration tool like sqla-yaml for declarative schema definitions if you need more control.
🎯 Key Takeaway
Integrate migration checks into CI, always backup before production migrations, and test downgrades.
● Production incidentPOST-MORTEMseverity: high

The Ghost Column That Broke Production

Symptom
Users saw 500 errors when trying to create new accounts. Logs showed 'column user.profile_pic does not exist'.
Assumption
The developer assumed the migration had been applied because they ran alembic upgrade head locally.
Root cause
The migration script was created but never committed to version control. The production deployment pipeline pulled the code but not the migration file.
Fix
Committed the missing migration file, re-ran alembic upgrade head on production, and added a CI check that verifies all migrations are applied before deployment.
Key lesson
  • Always commit migration scripts to version control.
  • Add a CI step to run alembic check or alembic current to ensure database is up-to-date.
  • Use environment-specific migration branches cautiously.
  • Test migrations in a staging environment first.
  • Document the migration workflow for the team.
Production debug guideSymptom to Action4 entries
Symptom · 01
Migration fails with 'Target database is not up to date'
Fix
Run alembic current to see current revision, then alembic upgrade head.
Symptom · 02
Autogenerate misses a change
Fix
Manually edit the migration script. Ensure model metadata is imported correctly.
Symptom · 03
Downgrade fails due to data loss
Fix
Add conditional logic in downgrade to handle data (e.g., backup before drop).
Symptom · 04
Merge conflict in migration branch
Fix
Resolve by editing the branch markers or creating a new merge migration.
★ Quick Debug Cheat SheetCommon Alembic issues and immediate fixes.
alembic upgrade head fails with 'No such revision'
Immediate action
Check alembic_version table
Commands
alembic current
alembic history
Fix now
Manually set alembic_version to a known revision or re-run from scratch.
Autogenerate not detecting changes+
Immediate action
Verify model imports in env.py
Commands
alembic revision --autogenerate -m 'test'
alembic upgrade head
Fix now
Add 'from myapp.models import Base' to env.py.
Migration script has syntax error+
Immediate action
Run the script manually
Commands
python -c "import migration_script"
alembic upgrade head --sql
Fix now
Fix the syntax and re-run.
FeatureAlembicDjango MigrationsFlyway
IntegrationSQLAlchemyDjango ORMAny JDBC database
AutogenerateYesYesNo (manual SQL)
LanguagePythonPythonJava/SQL
BranchingYes (merge)Yes (merge)Yes (versioned)
Data migrationsYes (op.execute)Yes (RunPython)Yes (SQL)
⚙ Quick Reference
7 commands from this guide
FileCommand / CodePurpose
setup_example.pyfrom alembic import contextSetting Up Alembic
migration_script.py"""add profile_pic columnUnderstanding Migration Scripts
autogenerate_example.pydef upgrade():Autogenerating Migrations
batch_migration.py"""add age column with defaultApplying and Rolling Back Migrations
merge_migration.py"""merge def and ghiBranching and Merging Migrations
data_migration.py"""populate full_name from first and last nameData Migrations and Custom Operations
ci_migration_check.pyresult = subprocess.run(['alembic', 'check'], capture_output=True, text=True)Production Best Practices

Key takeaways

1
Alembic provides version-controlled, repeatable database migrations for SQLAlchemy.
2
Use --autogenerate to detect schema changes, but always review and manually fix renames and data migrations.
3
Test both upgrade and downgrade in staging, and integrate migration checks into CI.
4
Handle branching with merge migrations and avoid unnecessary branches.
5
Backup production databases before applying migrations and have a rollback plan.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
What is Alembic and how does it integrate with SQLAlchemy?
Q02SENIOR
Explain the purpose of `down_revision` in a migration script.
Q03SENIOR
How would you handle a migration that renames a column without losing da...
Q04SENIOR
Describe a scenario where you would need a merge migration.
Q05SENIOR
How can you ensure data integrity during a data migration?
Q01 of 05JUNIOR

What is Alembic and how does it integrate with SQLAlchemy?

ANSWER
Alembic is a database migration tool for SQLAlchemy. It uses SQLAlchemy's metadata to detect schema changes and generate migration scripts. It tracks applied revisions in the alembic_version table.
FAQ · 5 QUESTIONS

Frequently Asked Questions

01
How do I rename a column with Alembic?
02
Can I use Alembic with Django?
03
What is the difference between `upgrade` and `downgrade`?
04
How do I handle circular dependencies in migrations?
05
What does `alembic current` show?
N
Naren Founder & Principal Engineer

20+ years shipping production Python across data and backend systems. Written from production experience, not tutorials.

Follow
Verified
production tested
July 15, 2026
last updated
2,398
articles · all by Naren
🔥

That's Python Libraries. Mark it forged?

3 min read · try the examples if you haven't

Previous
Async SQLAlchemy: asyncpg, Alembic, and Production Patterns
62 / 69 · Python Libraries
Next
SciPy: Scientific Computing in Python