I was investigating a production issue where a table column was missing. Turned out someone had merged a migration that dropped it without updating the application code. This is where knowing your way around Alembic’s history commands becomes essential.
Basic history commands Link to heading
Show migration history:
alembic history
This dumps the entire migration tree, which can be overwhelming on older projects. I rarely use this without filtering.
Show last 6 migrations (what I actually use):
alembic history -r -6:
Show a specific range:
alembic history -r base:head
Check current revision:
alembic current
This is probably the first command I run when debugging migration issues - it tells you exactly which revision the database is at.
Check for multiple heads (merge conflicts):
alembic check
If this returns anything, you’ve got problems. More on that below.
Show revision details:
alembic show abc123
This shows you the actual SQL that will run, which is invaluable before applying a scary-looking migration.
Dealing with migration conflicts Link to heading
When multiple people create migrations on different branches, you end up with multiple “heads” - essentially merge conflicts in your migration history. This happened to me last month when three of us were working on different features.
First, detect the problem:
alembic heads
If you see more than one head, you need to create a merge migration:
alembic merge -m "merge conflicting heads" head1 head2
This creates a new migration that brings both branches together. The merge migration itself usually doesn’t do anything - it just declares that both previous migrations have been applied.
Hot take: Alembic’s auto-detection of migration conflicts is brilliant, but the error messages are terrible. The first time I saw “multiple head revisions are present”, I had no idea what it meant. It should just say “merge conflict - run alembic merge”.
Downgrade strategy Link to heading
Here’s something I learned the hard way: always test your downgrades. I once deployed a migration with a broken downgrade, and when we needed to roll back, we couldn’t.
Test a downgrade without actually doing it:
alembic downgrade -1 --sql
The --sql flag shows you what would happen without executing it. Saved me multiple times.
Downgrade to a specific revision:
alembic downgrade abc123
Downgrade everything (nuclear option):
alembic downgrade base
I maintain a rule: if the downgrade is complex or involves data loss, I write it as a separate forward migration instead. Downgrading production is scary enough without worrying about buggy rollback logic.
My workflow Link to heading
When reviewing migration PRs, I:
- Check
alembic history -r -3:to see what’s changed - Run
alembic upgrade head --sqllocally to review the actual SQL - Check if there are any DDL operations that lock tables (looking at you,
ALTER TABLE) - Verify the downgrade works with
alembic downgrade -1 --sql
Common gotchas Link to heading
Autogenerate isn’t perfect: Alembic’s autogenerate misses things like custom types, check constraints, and sometimes even index changes. I always review the generated migration and add manual edits.
Migration order matters: If you’re working on a team, coordinate who’s creating migrations. A dedicated channel for coordinating migrations helps avoid conflicts.
Don’t edit applied migrations: Once a migration is in production, treat it as immutable. If you need to fix something, create a new migration. I’ve seen people edit old migrations and cause havoc when others pull the changes.
Further reading Link to heading
The official Alembic documentation is actually quite good. For practical patterns, the Alembic Cookbook covers common scenarios like handling multiple databases, working with partitions, and writing custom operations.
I also recommend reading the SQLAlchemy Migration Guide if you’re new to migrations in general - it explains the concepts behind revision chains better than most resources.