smaple.tr
database migration

Database Migration and Version Management: Flyway, Liquibase and CI/CD [2026]

Mehmet Kurtipek
November 5, 2025
11 min read
database migration
schema management
Flyway
Liquibase
database versioning
CI/CD

Most engineering teams version application code meticulously — every change reviewed, tested, and deployed through a CI/CD pipeline. The same teams often manage database schema changes by hand: manual SQL scripts, SSH into production servers, messages to the DBA. The result is predictable: schema drift between environments, rollback attempts that fail because the previous state was never captured, and incidents traceable to a database change that bypassed the deployment process.

Database migration and version management applies the same discipline to schema changes that code version control applies to application logic. Every change is versioned, repeatable, reversible, and automated. This guide covers the full migration management stack: the migration file format, Flyway and Liquibase as the two primary tools, zero-downtime migration patterns for large production tables, CI/CD pipeline integration, and the operational practices that prevent migration-related incidents.

Database Migration: The Core Problem

Schema changes without migration management create four recurring failure patterns:

Environment drift: Development, staging, and production databases diverge over time. A feature works in development but fails in production because the staging migration was applied and the production migration was missed, or applied differently.

No rollback path: When a schema change causes an incident, reverting the application is straightforward (deploy the previous version). Reverting the database is not — DROP COLUMN destroys data. Without planned rollback procedures, the incident response involves data recovery from backups rather than clean rollback.

Missing audit trail: No record of what schema change was made, when, by whom, and why. Root cause analysis after an incident becomes detective work through Slack history and handwritten notes.

New environment setup: A new developer joining the team cannot set up a local database that matches production without a documented sequence of schema changes applied in the correct order. Onboarding becomes hours of schema debugging.

Database migration tools solve all four problems by maintaining a versioned, ordered sequence of SQL changes applied consistently across all environments.

Flyway: Convention-Based Migration Management

Flyway is the most widely used database migration tool for Java and JVM-based stacks, though it integrates with any language via the CLI or Docker image.

Migration File Naming Convention

V1__create_users_table.sql
V2__add_email_index.sql
V3__add_orders_table.sql
V3.1__add_orders_index.sql
V4__add_customer_segments.sql
R__populate_reference_data.sql   # Repeatable: runs whenever content changes
U3__undo_add_orders_table.sql    # Undo (Pro feature): paired with V3

Version number: V<version>__<description>.sql. The double underscore separates version from description. Version numbers can be integers or decimals; Flyway applies them in order.

Repeatable migrations (R__) run after all versioned migrations when their checksum changes. Use for: stored procedures, views, and functions that you want to maintain as complete files rather than delta changes.

Flyway Configuration

# flyway.conf
flyway.url=jdbc:postgresql://localhost:5432/myapp
flyway.user=flyway_user
flyway.password=${DB_PASSWORD}
flyway.schemas=public
flyway.locations=filesystem:db/migrations
flyway.validateOnMigrate=true
flyway.outOfOrder=false  # Reject migrations applied out of sequence
flyway.baselineOnMigrate=false  # For existing databases: run flyway baseline first

Applying Migrations

# Check current status
flyway info

# Apply all pending migrations
flyway migrate

# Validate applied migrations against files (detect tampering)
flyway validate

# Rollback last migration (Pro, uses U migrations)
flyway undo

Flyway Schema History Table

Flyway maintains a flyway_schema_history table tracking every applied migration:

SELECT version, description, type, script, checksum, installed_on, success
FROM flyway_schema_history
ORDER BY installed_rank;

If success=false, the migration failed partway through. Fix the underlying issue and use flyway repair to remove the failed entry before retrying.

Liquibase: XML/YAML-Based Migration Management

Liquibase uses a changelog file format (XML, YAML, JSON, or SQL) that abstracts database-specific SQL, enabling database-agnostic migrations.

# changelog.yaml
databaseChangeLog:
  - changeSet:
      id: 1
      author: dev-team
      changes:
        - createTable:
            tableName: users
            columns:
              - column:
                  name: id
                  type: BIGSERIAL
                  constraints:
                    primaryKey: true
              - column:
                  name: email
                  type: VARCHAR(255)
                  constraints:
                    nullable: false
                    unique: true
              - column:
                  name: created_at
                  type: TIMESTAMPTZ
                  defaultValueComputed: NOW()

  - changeSet:
      id: 2
      author: dev-team
      changes:
        - addColumn:
            tableName: users
            columns:
              - column:
                  name: display_name
                  type: VARCHAR(100)
      rollback:
        - dropColumn:
            tableName: users
            columnName: display_name

Flyway vs Liquibase Decision

Criterion Flyway Liquibase
Migration format SQL files only XML/YAML/JSON/SQL
Database abstraction None (SQL is database-specific) Yes (abstracts DDL differences)
Rollback support Pro only (Undo migrations) Built-in rollback per changeset
Learning curve Low (familiar SQL) Medium (new format to learn)
Community Large, well-documented Large, enterprise-focused
Best for PostgreSQL/MySQL projects with SQL-native teams Multi-database environments, Java enterprise

For teams already comfortable with SQL and targeting a single database (PostgreSQL in most cases), Flyway's simplicity is an advantage. Liquibase's database abstraction matters primarily for organizations supporting migrations across PostgreSQL, MySQL, SQL Server, and Oracle simultaneously.

Zero-Downtime Migration Patterns

The most dangerous migrations are those that acquire table locks on large tables. An ALTER TABLE ADD COLUMN DEFAULT 'value' rewrites the entire table in PostgreSQL 10 and earlier — a table with 100 million rows can take 30+ minutes, locking the table for the duration.

Safe Column Addition Pattern

Unsafe (locks table, rewrites data):

ALTER TABLE orders ADD COLUMN discount_amount DECIMAL(10,2) DEFAULT 0.00 NOT NULL;

Safe (expand-contract pattern):

-- Step 1: Add nullable column (instant, no lock)
ALTER TABLE orders ADD COLUMN discount_amount DECIMAL(10,2);

-- Step 2: Backfill in batches (no table lock, low I/O impact)
DO $$
DECLARE
  batch_size INTEGER := 1000;
  last_id BIGINT := 0;
  max_id BIGINT;
BEGIN
  SELECT MAX(id) INTO max_id FROM orders;
  WHILE last_id < max_id LOOP
    UPDATE orders
    SET discount_amount = 0.00
    WHERE id > last_id AND id <= last_id + batch_size
      AND discount_amount IS NULL;
    last_id := last_id + batch_size;
    PERFORM pg_sleep(0.01);  -- Brief pause to reduce I/O pressure
  END LOOP;
END $$;

-- Step 3: Add NOT NULL constraint (safe after backfill in Postgres 12+)
ALTER TABLE orders ALTER COLUMN discount_amount SET NOT NULL;
-- Or set a default for future rows:
ALTER TABLE orders ALTER COLUMN discount_amount SET DEFAULT 0.00;

Safe Index Creation

CREATE INDEX acquires an AccessShareLock that blocks writes. On large tables, this causes application timeouts.

-- Unsafe: locks table during index creation
CREATE INDEX idx_orders_user_id ON orders (user_id);

-- Safe: concurrent index creation (no write lock)
CREATE INDEX CONCURRENTLY idx_orders_user_id ON orders (user_id);
-- CONCURRENTLY takes longer but does not block

Note: CREATE INDEX CONCURRENTLY cannot run inside a transaction. Flyway wraps each migration in a transaction by default; disable this for CONCURRENTLY operations:

-- V5__add_concurrent_index.sql
-- Flyway directive: run outside transaction
-- flyway:executeInTransaction=false
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_orders_user_id ON orders (user_id);

Column Rename Pattern

Renaming a column requires coordination with application code to avoid downtime:

  1. Add new column: ALTER TABLE orders ADD COLUMN new_name TYPE;
  2. Deploy application code that writes to both old and new column
  3. Backfill: UPDATE orders SET new_name = old_name WHERE new_name IS NULL;
  4. Deploy application code that reads from new column, still writes to both
  5. Deploy application code that reads and writes only from new column
  6. Drop old column: ALTER TABLE orders DROP COLUMN old_name;

CI/CD Integration

Migration validation and application in CI/CD prevents environment drift by ensuring every deployment to any environment applies the same migration sequence.

# GitHub Actions workflow: migrate.yml
name: Database Migration

on:
  push:
    branches: [main, staging]
  workflow_dispatch:

jobs:
  migrate:
    runs-on: ubuntu-latest
    env:
      DATABASE_URL: ${{ secrets.DATABASE_URL }}

    steps:
      - uses: actions/checkout@v4

      - name: Validate migrations
        uses: docker://flyway/flyway:latest
        with:
          args: -url=${{ env.DATABASE_URL }} validate

      - name: Apply migrations (dry run)
        uses: docker://flyway/flyway:latest
        with:
          args: -url=${{ env.DATABASE_URL }} -dryRunOutput=/tmp/migration-dry-run.sql migrate

      - name: Review dry run output
        run: cat /tmp/migration-dry-run.sql

      - name: Apply migrations
        uses: docker://flyway/flyway:latest
        with:
          args: -url=${{ env.DATABASE_URL }} migrate

Migration environment strategy:

  • Development: Flyway auto-migrates on application startup
  • Staging: Migrations run as a pre-deployment step in CI/CD, before application deployment
  • Production: Migrations run as a separate job with manual approval gate before application deployment

Pre-migration checklist for production:

  • Migration tested on staging with production-equivalent data volume
  • Backup verified recent and restorable
  • Zero-downtime pattern used for all table-locking operations
  • Rollback procedure documented and tested on staging

Multi-Database Environment Management

In real development workflows, multiple migration environments require consistent management. Developers work locally, feature branches deploy to staging, and production runs the validated set.

Environment-specific configuration:

# Use environment variables to configure database URL per environment
export FLYWAY_URL="jdbc:postgresql://${DB_HOST}/${DB_NAME}"
export FLYWAY_USER="${DB_USER}"
export FLYWAY_PASSWORD="${DB_PASSWORD}"

# Environment detection in scripts
ENVIRONMENT="${DEPLOY_ENV:-development}"
echo "Running migrations for environment: $ENVIRONMENT"
flyway migrate

Baseline for existing databases: When adopting Flyway on a database that already has a schema, use flyway baseline to mark the current state as version 1 without running any migration files:

flyway -url=$DB_URL -user=$DB_USER baseline
# This records the current state as the baseline version
# Future flyway migrate commands apply only migrations with version > baseline

Developer workflow for branching: When multiple developers work on feature branches simultaneously, version number conflicts occur if both add a migration with the same version number. Two solutions:

  1. Timestamp-based versioning: V20260401143022__add_discount_table.sql — eliminates conflicts because timestamps are unique per developer and per minute.

  2. Branch namespace: V4_feature_payments__add_discount_table.sql — namespaces prevent conflicts within a team.

The version comparison must still produce a consistent ordering, so timestamp-based versioning is more reliable for teams with frequent parallel development.

Testing migrations: Migrations should have automated tests that verify both the forward migration and (where applicable) the rollback. A simple test pattern:

  1. Start with a clean database schema
  2. Apply migrations up to version N-1
  3. Insert representative test data
  4. Apply migration N
  5. Verify the schema changed correctly and data is intact
  6. If rollback migration exists: apply it and verify data is preserved

Running this test on every pull request catches migration errors before they reach staging or production environments.

Common Migration Failures and Prevention

Failure Cause Prevention
Migration fails in production, not staging Different data in production triggers constraint violation Test with production-equivalent data on staging
Table lock causes timeout ALTER TABLE on large table without CONCURRENTLY Audit all migrations for lock-acquiring operations
Checksum mismatch Applied migration file was modified Never modify applied migration files; add new migration instead
Branching conflicts Two developers add V5__ in separate branches Use timestamps or coordination in version numbering
Missing rollback path DROP COLUMN or DROP TABLE applied Plan rollback for every destructive migration before applying

Migration Governance in Team Workflows

Beyond the tooling, migration governance requires process decisions about review and approval.

Code review for migrations: Every migration file should go through the same pull request review process as application code. Reviewers should check: Does the migration use zero-downtime patterns for large tables? Is there a rollback path for destructive operations? Is the migration idempotent (safe to run twice if the CI pipeline retries)?

Migration freeze windows: High-traffic periods (product launches, holiday peaks, scheduled maintenance windows) warrant a migration freeze. Communicate freeze windows proactively — migrations queued in development branches should not be merged during freeze periods.

Documentation requirement: Each migration file should include a comment explaining what the migration does and why it was needed. Future engineers reading the migration history should understand the business reason for each schema change, not just the SQL mechanics.

Conclusion

Database migration and version management is not optional for production systems. The cost of unmanaged schema changes compounds over time: environment drift, incident debugging without audit trails, and rollback attempts that cannot succeed because the previous state was never captured.

Flyway and Liquibase both solve this problem effectively. The choice between them is secondary to adopting either one. The zero-downtime patterns — batched backfills, CONCURRENTLY index creation, expand-contract column changes — are what separate migrations that deploy without incidents from migrations that cause them.


Author: Smart Maple Database Engineering Team Updated: April 2026

Related Articles

August 11, 2026

MLOps Guide: Taking Machine Learning Models to Production [2026]

87% of machine learning models built by data science teams never reach production. The models work — they pass cross-validation, they score well on holdout sets, they demonstrate genuine predictive value. The problem is not the modeling. The problem is everything that happens between a notebook experiment and a reliable, monitored, production system. MLOps is the discipline that closes that gap. This guide covers the full MLOps stack: maturity levels, tooling choices (MLflow, DVC, Kubeflow

Read More
August 10, 2026

LLM Fine-Tuning Guide: Custom Model Training with LoRA and QLoRA [2026]

General-purpose LLMs are impressive. They can write code, summarize documents, answer questions, and translate between languages with reasonable accuracy. But "reasonable" is not good enough when your application requires consistent output format, domain-specific terminology, a particular tone, or behavior that the base model was never trained to exhibit. That gap is where fine-tuning matters. Fine-tuning updates a model's weights on your specific data, changing how the model behaves — not

Read More
August 9, 2026

Computer Vision Applications: Object Detection, OCR, and Industrial AI [2026]

Computer vision has moved well past the research phase. The models are trained, the frameworks are mature, the hardware is accessible, and the use cases are generating measurable returns. What was a specialized capability requiring deep expertise in 2018 is now deployable infrastructure — if you know which component to reach for and where the real complexity lives. This guide covers computer vision applications across industrial, medical, logistics, and document processing domains. It expl

Read More