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..
20+ years shipping production Python across data and backend systems. Written from production experience, not tutorials.
- ✓Basic knowledge of Python and SQLAlchemy (see our SQLAlchemy basics tutorial).
- ✓A working Python environment with pip.
- ✓A database (PostgreSQL, MySQL, SQLite) for testing.
- Alembic is a lightweight database migration tool for SQLAlchemy.
- It tracks schema changes as versioned migration scripts.
- Use
alembic revision --autogenerateto auto-detect model changes. - Apply migrations with
alembic upgrade head. - Rollback with
alembic downgrade -1.
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 ``
Edit alembic.ini to set your database URL:
``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.
alembic upgrade head without first taking a database backup. Use a read-only replica to test migrations.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 and upgrade() functions. These functions use Alembic's Operations API to modify the schema. Common operations include downgrade()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).
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.
alembic check to ensure the migration script matches the models. This catches missing migrations early.--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.
alembic upgrade head on production without a recent backup. Use a maintenance window for long-running migrations.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 should be empty (or contain any needed conflict resolution). The upgrade() is usually a no-op.downgrade()
Branching is powerful but can lead to complex histories. Use feature branches with caution.
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 to reflect the table and perform bulk updates. Be careful with large datasets; consider batching.table()
You can also create custom operations by subclassing MigrateOperation. This is advanced but useful for reusable patterns.
op.execute() with LIMIT and OFFSET.op.execute() for data migrations. For complex logic, use SQLAlchemy Core. Always test downgrade data integrity.Production Best Practices
- Version control: Always commit migration scripts. Never generate them on production.
- CI checks: Run
alembic checkto ensure models match the latest migration. Also runalembic upgrade headin CI against a test database. - Idempotency: Write migrations that can be run multiple times safely (use
if not column_existschecks). - Backup: Before any migration in production, take a database backup.
- Rollback plan: Test downgrade scripts. Have a manual rollback procedure.
- Environment-specific: Use different migration branches for different environments if needed, but prefer a linear history.
- Locking: Long-running migrations can lock tables. Use
--sqlto generate SQL and run manually with care. - Monitoring: Log migration output and monitor for errors. Use tools like
alembic currentin health checks.
Example CI script:
``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 ``
sqla-yaml for declarative schema definitions if you need more control.The Ghost Column That Broke Production
alembic upgrade head locally.alembic upgrade head on production, and added a CI check that verifies all migrations are applied before deployment.- Always commit migration scripts to version control.
- Add a CI step to run
alembic checkoralembic currentto 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.
alembic current to see current revision, then alembic upgrade head.alembic currentalembic history| File | Command / Code | Purpose |
|---|---|---|
| setup_example.py | from alembic import context | Setting Up Alembic |
| migration_script.py | """add profile_pic column | Understanding Migration Scripts |
| autogenerate_example.py | def upgrade(): | Autogenerating Migrations |
| batch_migration.py | """add age column with default | Applying and Rolling Back Migrations |
| merge_migration.py | """merge def and ghi | Branching and Merging Migrations |
| data_migration.py | """populate full_name from first and last name | Data Migrations and Custom Operations |
| ci_migration_check.py | result = subprocess.run(['alembic', 'check'], capture_output=True, text=True) | Production Best Practices |
Key takeaways
--autogenerate to detect schema changes, but always review and manually fix renames and data migrations.Interview Questions on This Topic
What is Alembic and how does it integrate with SQLAlchemy?
alembic_version table.Frequently Asked Questions
20+ years shipping production Python across data and backend systems. Written from production experience, not tutorials.
That's Python Libraries. Mark it forged?
3 min read · try the examples if you haven't