Cheatsheet for the Alembic CLI.

Init

alembic init migrations              # sync template
alembic init -t async migrations     # async template

Revision (create migration)

alembic revision -m "msg"                       # empty migration
alembic revision --autogenerate -m "msg"        # generate from model diff
alembic revision --autogenerate --rev-id abc123 -m "msg"  # custom rev ID

Upgrade / downgrade

alembic upgrade head           # latest
alembic upgrade +1             # one step forward
alembic upgrade <rev>          # specific revision

alembic downgrade -1           # one step back
alembic downgrade <rev>        # to specific
alembic downgrade base         # all the way down

State queries

alembic current                # current revision
alembic current --verbose      # with details

alembic heads                  # list head revisions (>1 = branched)
alembic heads --verbose

alembic history                # full history
alembic history --verbose
alembic history -r <from>:<to>

Show specific revision

alembic show <rev>
alembic show head
alembic show base

Stamp (mark as applied without running)

alembic stamp head             # mark current = head
alembic stamp <rev>            # mark current = specific
alembic stamp base             # mark current = base (empty)

Use cases:

  • Bootstrapping a fresh DB created via Base.metadata.create_all().
  • Recovering after manual schema fixes.

Branches

alembic merge <rev1> <rev2> -m "merge X+Y"
alembic branches               # show branch points

Offline mode

alembic upgrade head --sql > migration.sql   # generate SQL without applying

For manual review or running SQL elsewhere.

Dry run

alembic upgrade head --sql > /dev/null       # validates without applying

With env vars

DATABASE_URL=postgresql://... alembic upgrade head

If env.py reads from env / Settings.

With config override

alembic -x url=postgresql://... upgrade head

Useful in CI for multi-tenant migrations.

Pythonic API

from alembic.config import Config
from alembic import command

cfg = Config("alembic.ini")
cfg.set_main_option("sqlalchemy.url", DATABASE_URL)

command.upgrade(cfg, "head")
command.revision(cfg, autogenerate=True, message="msg")
command.downgrade(cfg, "-1")
command.stamp(cfg, "head")
command.current(cfg)
command.history(cfg)

For running migrations programmatically (e.g., in tests).

In tests

import pytest
from alembic.config import Config
from alembic import command

@pytest.fixture
def migrated_db(postgres):
    cfg = Config("alembic.ini")
    cfg.set_main_option("sqlalchemy.url", postgres.get_connection_url())
    command.upgrade(cfg, "head")
    return postgres

In K8s Job (pre-deploy)

apiVersion: batch/v1
kind: Job
metadata:
  name: migrate-{{ .Values.image.tag }}
  annotations:
    "helm.sh/hook": pre-install,pre-upgrade
spec:
  template:
    spec:
      containers:
        - name: migrate
          image: myapp:{{ .Values.image.tag }}
          command: ["alembic", "upgrade", "head"]
          envFrom:
            - secretRef: { name: app-secrets }
      restartPolicy: OnFailure
  backoffLimit: 3

CI verification

alembic upgrade head           # apply all migrations against test DB
# Check single head
[ "$(alembic heads | wc -l)" -eq 1 ] || exit 1
# Run tests
pytest

Common mistakes

  • alembic upgrade head against prod without staging test.
  • Multiple heads after merge — pick one via alembic merge.
  • alembic stamp without understanding consequences — DB may be at wrong state.
  • Running migrations from app startup with N replicas — race.

Read this next

If you want my migration command runbook, it’s at rajpoot.dev .


Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .