Without migrations you change the schema manually in production. With migrations you have a versioned, repeatable, reversible history of changes.
Alembic (Python/SQLAlchemy)¶
Database Migrations¶
alembic init migrations
Generate migration¶
alembic revision –autogenerate -m “add users table”
Apply¶
alembic upgrade head alembic downgrade -1 # Rollback
Migration Example¶
def upgrade(): op.create_table(‘users’, sa.Column(‘id’, sa.Integer, primary_key=True), sa.Column(‘email’, sa.String(255), unique=True, nullable=False), sa.Column(‘name’, sa.String(100)), sa.Column(‘created_at’, sa.DateTime, server_default=sa.func.now()), ) op.create_index(‘idx_users_email’, ‘users’, [‘email’]) def downgrade(): op.drop_table(‘users’)
Best Practices¶
- Migrations in source control
- Idempotent (safe to run multiple times)
- Backward compatible (deploy before migration)
- Test on a copy of production data
- Never drop a column directly — deprecated → remove
Key Takeaway¶
Migrations = versioned schema. Alembic for Python, Flyway for Java, Prisma Migrate for TypeScript.