Use whenever a schema is managed with Flyway: versioned, repeatable and undo migration files, migrate, info, validate, baseline and repair, the schema history table, migration ordering questions, and CI gates on schema changes, via CLI, Maven, Gradle or Docker.

/plugin install qa-db-migrations@testland-qa

First time in Claude Code? Add the marketplace with /plugin marketplace add testland/qa

View source (opens in a new tab)

Overview

Flyway tracks applied migrations in a per-database flyway_schema_history table and applies pending migrations in order by version number (fw-how (opens in new window)).

When to use

  • The repo has a db/migration/ (or configured) directory of V*.sql / R*.sql files.
  • The user works with Flyway CLI / Docker / Maven plugin / Gradle plugin.
  • A CI workflow needs a migration gate against a per-PR ephemeral database (e.g., Testcontainers) before merge.
  • The team migrates from manual SQL scripts to versioned migration control.

How to use

Follow Steps 1 - 7 below in order; each numbered step is the single source for that part of the workflow.

Step 1 - Install

Flyway runs on Windows, macOS, Linux, and Docker, plus Maven and Gradle plugin distributions (fw-home (opens in new window)). Common install paths:

# Docker (zero-install for CI)
docker run --rm -e FLYWAY_PASSWORD flyway/flyway -url=jdbc:postgresql://host/db -user=usr migrate

# Homebrew (macOS / Linux)
brew install flyway

# Maven plugin (Spring Boot etc.)
# add to pom.xml under <build><plugins>

Never put the password on the command line or in a committed file. Flyway reads it from FLYWAY_PASSWORD (fw-pass (opens in new window)), and per docker-env (opens in new window) a bare -e FLYWAY_PASSWORD forwards the host's value into the container; CI sets it from its secret store.

Step 2 - First migration

Migrations may be written in SQL, Java, or other scripting languages (fw-how (opens in new window)). File naming places migrations in the configured locations (default db/migration):

db/migration/
├── V1__create_users.sql
├── V2__add_email_index.sql
├── R__refresh_active_users_view.sql      # repeatable, reruns on checksum change
└── U1__remove_users.sql                   # undo (Flyway Teams)

The prefix scheme:

PrefixTypeReruns?Use
V<n>__VersionedOnceNew schema changes; immutable after merge
R__RepeatableWhen checksum changesViews / stored procs / seed data
U<n>__UndoInverse of versionedRollback (Teams edition)

__ (double underscore) separates version + description; .sql (or configured suffix) marks the file as a migration.

Step 3 - Core commands

The daily loop is flyway info (preview pending) -> flyway migrate (apply pending) -> flyway validate (checksum-verify applied files before deploy). After a failed migration, flyway repair fixes flyway_schema_history - never edit an applied file. Full command reference (baseline, undo, clean, and the rest): references/commands.md.

Step 4 - Pending-migration semantics

Migrations with a version lower than the history table's current version are ignored by default; the rest are pending - available but not applied (fw-how (opens in new window)). Safety property: a developer who pulls main and runs flyway migrate applies only the new migrations; those already in flyway_schema_history are not re-run.

Step 5 - Configuration

Configuration via flyway.conf file, env vars (FLYWAY_*), or CLI flags. Key settings:

flyway.url=jdbc:postgresql://localhost:5432/mydb
flyway.user=myuser
# no flyway.password here - export FLYWAY_PASSWORD instead (Step 1)
flyway.locations=filesystem:db/migration,classpath:db/migration
flyway.baselineOnMigrate=true        # auto-baseline empty schemas
flyway.cleanDisabled=true            # CRITICAL for prod - disable destructive `clean`
flyway.outOfOrder=false              # reject migrations with versions lower than max applied
flyway.validateOnMigrate=true        # checksum-validate before applying

cleanDisabled=true is a mandatory production guard - flyway clean drops every object in the schema. Always set this in production config; only enable for ephemeral test databases.

Step 6 - CI integration

Gate every PR on an ephemeral DB (Docker / Testcontainers): spin the DB, apply migrations, run tests against the migrated schema. The full GitHub Actions job and the Testcontainers @BeforeAll pattern are in references/commands.md.

Step 7 - Composition with sister tools

Before merge, apply adversarial review of new migrations - classify each as additive / breaking / data-loss / locking.

Worked example

Add an index on users.email and ship it through CI:

  1. Create db/migration/V2__add_email_index.sql with CREATE INDEX idx_users_email ON users(email);.
  2. Locally run flyway info - it lists V2 as pending while V1 shows applied.
  3. flyway migrate applies only V2 and appends a row to flyway_schema_history with its checksum.
  4. In the PR, CI starts an ephemeral Postgres, runs the Docker flyway ... migrate, then mvn test against the migrated schema.
  5. A teammate later edits the merged V2 file; on their next run flyway validate fails with a checksum mismatch, so they add V3__... instead of mutating V2.

Anti-patterns

Anti-patternWhy it failsFix
Edit a previously-applied versioned migrationChecksum mismatch; validate fails on next runAdd a new V_n+1 migration that adjusts
cleanDisabled=false in production configOne stray flyway clean drops the schemaAlways cleanDisabled=true (Step 5)
Mixing versioned + repeatable migrations for the same objectRepeatable applies after every versioned change → racePick one per object class
outOfOrder=true without team agreementLower-version migrations apply mid-stream; ordering breaksDefault false; enable per change with team review
Skip CI gating on per-PR ephemeral DBMigrations break in production for the first timeAlways run migrations in CI (Step 6)

Limitations

  • Undo migrations are a Teams (paid) feature - OSS users implement rollback manually via inverse versioned migrations.
  • flyway clean is irreversible; the cleanDisabled=true guard is the only protection.
  • 50+ supported DBMS but rule depth varies - consult per-database pages on fw-home (opens in new window) for vendor-specific syntax.
  • Requires JVM (CLI bundles its own JRE; Docker / Maven plugin inherit it).

References

Reference

Flyway command reference and CI integration

View source (opens in a new tab) (opens in new window)

Command reference

Per fw-home (opens in new window), Flyway's commands are Migrate, Clean, Info, Validate, Undo, Baseline, Repair, Check, and Snapshot.

CommandUse
flyway migrateApply pending migrations
flyway infoShow applied + pending migration list
flyway validateVerify checksums of applied migrations vs disk files
flyway baselineMark a legacy schema state as baseline (skip prior migrations)
flyway repairFix a broken flyway_schema_history (e.g., after a failed migration)
flyway undoRoll back the last versioned migration (Teams)
flyway cleanDrop all objects in the schema (production-disabled by default)

CI integration

Pattern: ephemeral DB (Docker / Testcontainers) per PR, apply migrations, run tests against the migrated schema.

- name: Spin up Postgres
  uses: docker/setup-buildx-action@v3
- run: docker run -d --name pg -p 5432:5432 -e POSTGRES_PASSWORD="$FLYWAY_PASSWORD" postgres:16
  env:
    FLYWAY_PASSWORD: ${{ secrets.CI_DB_PASSWORD }}
- name: Apply migrations
  env:
    FLYWAY_PASSWORD: ${{ secrets.CI_DB_PASSWORD }}
  run: |
    docker run --rm --network=host -e FLYWAY_PASSWORD \
      -v "$PWD/db/migration:/flyway/sql" \
      flyway/flyway -url=jdbc:postgresql://localhost:5432/postgres \
      -user=postgres migrate
- name: Run tests
  run: mvn test

Both steps read the password from the CI_DB_PASSWORD repository secret, so no literal value lands in the workflow file.

For full integration with testcontainers (in the qa-test-environment plugin): spin up the DB via Testcontainers, then call Flyway.configure() in JUnit @BeforeAll.