Applying migrations and rolling them back are the two most frequent Alembic operations. alembic upgrade head applies all pending migrations in order; alembic downgrade -1 rolls back the most recent one. Understanding the migration chain, how Alembic determines the current state, and how to safely handle rollbacks โ including the case where downgrade is difficult or impossible โ gives you the confidence to evolve your schema without fear.
Applying Migrations
# โโ Apply all pending migrations โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
alembic upgrade head
# "head" means the latest migration in the chain
# Runs all unapplied migrations in order from current version to head
# โโ Apply a specific number of steps โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
alembic upgrade +1 # apply the next one migration
alembic upgrade +2 # apply the next two migrations
# โโ Apply up to a specific revision โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
alembic upgrade abc123def456 # apply up to and including this revision
# โโ Preview the SQL without executing it โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
alembic upgrade head --sql # prints SQL to stdout, does not run it
# โโ Check what would be applied โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
alembic current # show current applied revision
alembic history # show all revisions with applied marker
alembic show abc123 # show details of a specific revision
alembic_version in your database. When you run alembic upgrade head, Alembic reads the current version from this table, finds all migrations with a higher version (i.e., those after the current one in the chain), and applies them in order. If the alembic_version table does not exist (fresh database), Alembic creates it and runs all migrations from the beginning. This makes migrations idempotent โ safe to run on any database at any state.alembic upgrade head --sql to preview the exact SQL that will run before applying it. This is especially useful before production deployments โ you can review the SQL, estimate how long it will take (a large table ADD COLUMN might take seconds or minutes), and decide whether to schedule a maintenance window. The SQL output also helps you verify that generated migrations are correct before touching the production database.alembic downgrade base rolls back ALL migrations โ essentially dropping all tables and recreating from scratch. This is appropriate for test environments but catastrophically destructive in production. Always specify an exact revision for production rollbacks: alembic downgrade -1 (one step back) or alembic downgrade abc123 (to a specific known-good revision). Never use base in production.Rolling Back
# โโ Roll back the most recent migration โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
alembic downgrade -1
# โโ Roll back two migrations โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
alembic downgrade -2
# โโ Roll back to a specific revision โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
alembic downgrade abc123def456 # reverts to state at abc123def456
# โโ Roll back ALL migrations (development/test only!) โโโโโโโโโโโโโโโโโโโโโโโโโ
alembic downgrade base # NEVER in production โ drops everything!
# โโ Preview what downgrade SQL would run โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
alembic downgrade -1 --sql
The Migration Chain
Migration chain (each revision points to its parent):
base โ rev001 โ rev002 โ rev003 โ rev004 (head)
โ current (alembic_version table)
alembic upgrade head: applies nothing (already at head)
alembic downgrade -1: reverts rev004, current becomes rev003
alembic downgrade -1: reverts rev003, current becomes rev002
alembic upgrade head: applies rev003 then rev004
# After a bad migration was applied:
# 1. alembic downgrade -1 โ revert the bad migration
# 2. Fix the migration script (or write a new one)
# 3. alembic upgrade head โ reapply
Handling Irreversible Migrations
from alembic.operations import MigrationScript
def upgrade() -> None:
# Add a NOT NULL column with a default โ fine to run
op.add_column("posts", sa.Column("is_featured", sa.Boolean(),
nullable=False, server_default="false"))
# Remove server_default after data is populated (now truly NOT NULL)
op.alter_column("posts", "is_featured", server_default=None)
def downgrade() -> None:
op.drop_column("posts", "is_featured")
# โโ Migration that loses data on downgrade โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
def upgrade() -> None:
# Archive and then delete old data
op.execute(
"INSERT INTO post_archive SELECT * FROM posts WHERE deleted_at IS NOT NULL"
)
op.execute("DELETE FROM posts WHERE deleted_at IS NOT NULL")
op.drop_column("posts", "deleted_at")
def downgrade() -> None:
# Cannot restore deleted data โ document this explicitly
op.add_column("posts", sa.Column("deleted_at", sa.TIMESTAMP(timezone=True),
nullable=True))
# NOTE: data in post_archive could be restored here, but deleted rows are gone
# This downgrade restores the column structure but NOT the deleted data
Common Mistakes
Mistake 1 โ Running downgrade base in the wrong environment
โ Wrong โ drops all tables in production:
DATABASE_URL=postgresql://prod-db... alembic downgrade base # destroys everything!
โ Correct โ always use specific revisions in production:
DATABASE_URL=postgresql://prod-db... alembic downgrade -1 # โ controlled rollback
Mistake 2 โ Editing an already-applied migration
โ Wrong โ changing a migration that has already run:
Editing alembic/versions/abc123_add_column.py after it was applied.
Alembic will not re-run it โ the database now differs from the script!
โ Correct โ create a new migration to fix the issue. Never edit applied migrations.
Mistake 3 โ Forgetting to write the downgrade function
โ Wrong โ cannot roll back:
def downgrade() -> None:
pass # no rollback possible โ dangerous in production!
โ Correct โ always implement a meaningful downgrade, even if it just logs a warning about data loss.
Quick Reference
| Task | Command |
|---|---|
| Apply all | alembic upgrade head |
| Apply one | alembic upgrade +1 |
| Preview SQL | alembic upgrade head --sql |
| Roll back one | alembic downgrade -1 |
| Roll back to revision | alembic downgrade abc123 |
| Current version | alembic current |
| Full history | alembic history --verbose |