Skip to main content

Upgrade and Downgrade: Alembic Revision Management

After writing migrations, you need to apply them to your database. Alembic tracks which revisions have been applied using a version table and gives you fine-grained control over moving forward and backward through your migration history. Understanding upgrade, downgrade, and revision dependencies is critical for safe deployments and local development.

The Alembic Version Table

When you run your first migration, Alembic creates an alembic_version table in your database:

 version_num
-----------------------------------
001a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5
002f9a8b7c6d5e4f3g2h1i0j9k8l7m6n
003x2y3z4a5b6c7d8e9f0g1h2i3j4k5l

Each row is the revision ID (a hash) of an applied migration. Alembic reads this table to know which migrations have run, which are pending, and what the current version is.

View Migration History

Check which migrations have been applied:

alembic current

Output:

003x2y3z4a5b6c7d8e9f0g1h2i3j4k5l (head)

View the full history including all available revisions:

alembic history --verbose

Output:

Rev ID: 001a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5 (001_initial_schema.py)
Parent: <base>
Path: migrations/versions/001_initial_schema.py
... docstring for the migration ...

Rev ID: 002f9a8b7c6d5e4f3g2h1i0j9k8l7m6n (002_add_phone_column.py)
Parent: 001a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5
Path: migrations/versions/002_add_phone_column.py
...

Rev ID: 003x2y3z4a5b6c7d8e9f0g1h2i3j4k5l (003_create_posts_table.py)
Parent: 002f9a8b7c6d5e4f3g2h1i0j9k8l7m6n
Path: migrations/versions/003_create_posts_table.py
...

This shows the chain of migrations: each has a parent, and each one depends on the previous one completing successfully.

Upgrade to Latest

Apply all pending migrations:

alembic upgrade head

This runs every migration from the current version to head (the latest). Output:

INFO  [alembic.runtime.migration] Context impl PostgreSQLImpl
INFO [alembic.runtime.migration] Will assume transactional DDL is supported by the environment
INFO [alembic.migration] Running upgrade 002f9a8b... 003x2y3z..., add_email_column

Upgrade to a Specific Revision

If you have three migrations but want to apply only the first two:

alembic upgrade 002f9a8b7c6d5e4f3g2h1i0j9k8l7m6n

This applies migrations up to (and including) revision 002f.... Useful for testing or rolling out changes gradually in production.

You can also use a relative revision notation:

alembic upgrade +2  # Apply the next 2 pending migrations

Downgrade Strategies

Rollback is critical for handling broken deployments. Alembic supports several approaches.

Downgrade by One Revision

Revert the last applied migration:

alembic downgrade -1

This runs the downgrade() function in the most recent migration. Useful for quick local testing or rolling back the last deployed change.

Downgrade Multiple Revisions

Roll back the last 3 migrations:

alembic downgrade -3

Downgrade to a Specific Revision

Roll back to a specific point in history:

alembic downgrade 001a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5

This runs the downgrade() function in each migration from the current version down to (but not including) the specified revision. All data in schema changes made after that revision are lost, so use with care.

Downgrade to Base (Erase Everything)

Drop all tables and indexes:

alembic downgrade base

This is destructive and rarely used in production. Useful for development/testing to reset your database to a clean state.

Pre-Deployment Testing

Before deploying migrations to production, test them on a staging database:

# On staging: apply migrations
alembic upgrade head

# Verify your application works with the new schema
# Run your test suite
pytest

# Test that downgrade works (in case you need to rollback)
alembic downgrade -1
alembic upgrade head

This confirms both upgrade and downgrade work before production deployment.

Viewing Migration SQL Without Applying

See the exact SQL that will run before you execute it:

alembic upgrade head --sql

Output (example):

-- Running upgrade 002f9a8b... 003x2y3z...

ALTER TABLE users ADD COLUMN email VARCHAR(100);

-- Running upgrade 003x2y3z... 004w1v2u3t4s...

CREATE TABLE posts (
id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
title VARCHAR(200) NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY(user_id) REFERENCES users (id)
);

-- ...

No SQL is actually executed; you're just seeing what will happen. Good for code review.

Handling Migration Conflicts in Teams

When multiple developers create migrations for the same feature, they may have conflicting revision IDs or dependencies. Example conflict:

Developer A creates: 003_add_admin_column.py
Developer B creates: 003_add_status_enum.py # Same revision number!

When you merge both branches, you have two 003_* files. Alembic detects this when you try to run alembic upgrade head and fails.

Resolving Conflicts

  1. Decide the order: A's migration should come before B's.
  2. Rename B's file to 004_add_status_enum.py.
  3. Edit B's migration to reference A's revision as its parent:
# migrations/versions/004_add_status_enum.py
"""add status enum"""
from alembic import op

# Indicate that this migration depends on the previous one
revision = '004x9y8z7a6b5c4d3e2f1g0h9i8j7k6l'
down_revision = '003x8y7z6a5b4c3d2e1f0g9h8i7j6k5l' # A's revision
branch_labels = None
depends_on = None
  1. Test: alembic upgrade head should apply both migrations in order.

Migration Metadata and Branching

Every migration file has metadata at the top:

# migrations/versions/003_add_email_column.py
"""add email column"""

revision = '003x2y3z4a5b6c7d8e9f0g1h2i3j4k5l'
down_revision = '002f9a8b7c6d5e4f3g2h1i0j9k8l7m6n'
branch_labels = None
depends_on = None
  • revision — unique hash for this migration
  • down_revision — the migration before this one (the parent)
  • branch_labels — for managing branching (advanced; see next article)
  • depends_on — explicit dependencies on other migrations

Do not edit these by hand; Alembic generates them automatically when you run alembic revision.

Autogenerating with Existing Revisions

When you have existing migrations and want to autogenerate a new one, run:

alembic revision --autogenerate -m "Add user status"

Alembic automatically determines the parent revision (the current head) and creates the new migration with the correct down_revision value.

Key Takeaways

  • alembic current shows the currently applied revision
  • alembic upgrade head applies all pending migrations in order
  • alembic upgrade <revision> applies up to a specific revision
  • alembic downgrade -1 rolls back the last migration; -N rolls back N migrations
  • Use alembic upgrade head --sql to preview SQL before applying
  • Test upgrade and downgrade on staging before production deployments
  • In teams, resolve conflicting revision numbers by renaming and updating the down_revision field

Frequently Asked Questions

Can I skip a migration without breaking the chain?

No. Alembic enforces a linear chain: each migration depends on its parent. If you skip revision 2, revision 3 won't run because its parent is missing. If you must skip a migration, mark it as already applied with alembic stamp <revision>, then create a new migration that undoes the changes.

What if a migration fails partway through?

Alembic wraps each migration in a transaction. If any statement fails, the entire migration is rolled back and your database remains in its previous state. Your migration either fully applies or not at all (atomic semantics).

How do I migrate a database without running a migration?

Use alembic stamp <revision>. This marks a revision as applied in the alembic_version table without running any SQL. Useful if you've manually applied changes or if you're starting to use Alembic on an existing database.

Can I reorder migrations that have already been applied?

No. Never reorder applied migrations. The down_revision chain is immutable once applied. If you need to "undo" an old migration, create a new migration that reverses its changes.

Further Reading