Database Migration Tools: Alembic, Flyway, Liquibase, Versioning
Database Migration Tools: Alembic, Flyway, Liquibase, Versioning
Database migrations are the practice of version-controlling schema changes. A good migration tool applies changes in order, handles conflicts, and supports rollback. This article compares the three leading tools and covers migration strategies.
Why Migrations?
Without migrations, schema changes are applied manually or through ad-hoc scripts:
* "Did we run the `ALTER TABLE` on staging?"
* "Which servers have the new index?"
* "The deploy failed because the migration hadn't run yet."
Migrations solve these problems by making schema changes repeatable, versioned, and automated.
Alembic
Alembic is the migration tool for SQLAlchemy (Python). It generates migration scripts from model definitions and supports auto-generation.
Setup
# alembic.ini
[alembic]
script_location = alembic
sqlalchemy.url = postgresql://user:pass@localhost/mydb
alembic init alembic
Creating a Migration
# alembic/versions/0001_create_users.py
"""create users table
Revision ID: 0001
Revises: None
Create Date: 2026-05-12
"""
from alembic import op
import sqlalchemy as sa
def upgrade():
op.create_table(
'users',
sa.Column('id', sa.Integer(), primary_key=True),
sa.Column('email', sa.String(255), 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'], unique=True)
def downgrade():
op.drop_index('idx_users_email')
op.drop_table('users')
Auto-Generation
# Generate migration from model changes
alembic revision --autogenerate -m "add avatar column"
This compares the current database state against SQLAlchemy models and generates `upgrade()` and `downgrade()` functions.
Running Migrations
alembic upgrade head # Apply all pending migrations
alembic upgrade +2 # Apply next 2 migrations
alembic downgrade -1 # Rollback 1 migration
alembic history # View migration history
alembic current # Show current revision
Flyway
Flyway is a Java-based migration tool that works with SQL scripts. It is simple, opinionated, and supports multiple databases.
Directory Structure
sql/
V1__create_users.sql
V2__add_avatar_column.sql
V3__create_orders_table.sql
Migration Script
-- V1__create_users.sql
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
name VARCHAR(100),
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- V2__add_avatar_column.sql
ALTER TABLE users ADD COLUMN avatar_url VARCHAR(500);
Configuration
# flyway.conf
flyway.url = jdbc:postgresql://localhost:5432/mydb
flyway.user = app_user
flyway.password = secure_password
flyway.locations = filesystem:sql
flyway.baselineOnMigrate = true
Running
flyway migrate
flyway undo # Flyway Teams only
flyway info # Show migration status
flyway validate # Check for changes to applied migrations
Checksum Validation
Flyway stores a checksum of each migration script. If a script is modified after being applied, `flyway validate` detects the change and warns:
> If a deployed migration is edited, Flyway catches it and refuses to apply further migrations until the discrepancy is resolved.
Liquibase
Liquibase supports multiple change formats: SQL, XML, YAML, and JSON. It is the most flexible but most verbose option.
Changeset (XML)
xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
Changeset (YAML)
databaseChangeLog:
- changeSet:
id: 2
author: bob
changes:
- addColumn:
tableName: users
columns:
- column:
name: avatar_url
type: varchar(500)
Rolling Back
liquibase rollbackCount 1 # Rollback 1 changeset
liquibase rollbackToDate 2026-05-10T12:00:00 # Rollback to specific date
liquibase rollback --tag v1.0 # Rollback to tagged release
Tool Comparison
| Feature | Alembic | Flyway | Liquibase | |---------|---------|--------|-----------| | Language | Python | Java/SQL | Java | | Auto-generation | Yes (from models) | No | No | | Rollback | Code-defined | Undo migration | Rollback changeset | | CI/CD integration | Pipeline script | Maven/Gradle/CLI | Maven/Gradle/CLI | | Complex logic | Python code | SQL only | SQL + XML/YAML | | Learning curve | Medium | Low | Medium | | Database support | SQLAlchemy-supported | 20+ databases | 15+ databases |
Migration Strategy Patterns
Linear Migrations
The simplest pattern: each migration depends on the previous one.
V1 → V2 → V3 → V4
Branching with Merges
For larger teams, branches create divergent migration histories:
V1 → V2 → V3 → V4 (main branch)
└→ V2a → V2b (feature branch)
When the feature branch merges, use `flyway merge` or adjust `Alembic` revision dependencies.
Repeatable Migrations
Views, functions, and stored procedures benefit from repeatable migrations that reapply on every change:
-- Flyway: R__update_user_view.sql
CREATE OR REPLACE VIEW active_users AS
SELECT * FROM users WHERE deleted_at IS NULL;
# Alembic: revision_identifiers.py with %(revision)s
# Or mark as "revision_identifiers = False" for repeatable patterns
Best Practices
* **One change per migration**: Adding a column and creating a table in the same migration makes rollback harder.
2\. **Test both directions**: Always test `upgrade()` and `downgrade()` before merging. 3\. **Use CI validation**: Run `flyway validate` or `alembic check` in CI to catch mistakes. 4\. **Avoid long-running locks**: Use `CREATE INDEX CONCURRENTLY` instead of `CREATE INDEX` in migrations. 5\. **Never modify applied migrations**: Create a new migration to fix issues. 6\. **Database-as-code**: Store migrations in version control alongside application code.
Database migrations bring schema changes into the software development lifecycle. Choose the tool that fits your language ecosystem, establish clear conventions, and make schema changes as routine as code changes.