Safe Alembic Migrations in Production: A Practical Guide

1 minute read

Published:

Running database migrations in production is one of those tasks that’s routine until it isn’t. Here’s the approach I use with Alembic to keep migrations safe and reversible.

The Golden Rules

1. Never edit a migration that has already been applied to production.

If a migration is live, create a new one to fix it. Editing applied migrations breaks the revision chain and causes alembic upgrade head to fail or apply unexpected changes.

2. Always test the downgrade.

Write downgrade() properly, not just pass. You’ll need it the day a deploy goes wrong:

def downgrade():
    op.drop_column('users', 'new_column')  # explicit
    # NOT just: pass

3. Check current revision before deploying.

alembic current
alembic history --verbose

Always verify the database is at the expected revision before running upgrade head in CI/CD.

The Pattern I Use in Docker

In ViraClip, migrations run as a one-shot step before the API starts:

# docker-compose.yaml
backend:
  command: >
    sh -c "alembic upgrade head &&
           uvicorn src.main:app --host 0.0.0.0 --port 8000"
  depends_on:
    postgres:
      condition: service_healthy

This ensures the schema is always up to date when the API starts, and the service_healthy condition prevents migrations from running before PostgreSQL is ready.

Dangerous Operations to Avoid

OperationWhy dangerousSafe alternative
DROP COLUMN immediatelyOld code still references itDeprecate first, drop in next deploy
Rename column in one stepBreaks existing queriesAdd new column, migrate data, drop old
Add NOT NULL without defaultFails on existing rowsAdd with default, then remove default

Zero-Downtime Strategy

For tables with millions of rows, use op.execute() with ALTER TABLE ... ADD COLUMN and a DEFAULT to avoid locking:

def upgrade():
    op.execute(
        "ALTER TABLE videos ADD COLUMN processed BOOLEAN DEFAULT FALSE NOT NULL"
    )

This is faster than the ORM-generated equivalent and avoids table locks on PostgreSQL 11+.