liquibase-migrations
Authors and runs Liquibase database migrations - changelog-driven schema management with changesets in XML / YAML / JSON / SQL formats; supports `liquibase update` / `status` / `rollback` / `tag` / `history` lifecycle; offers per-changeset preconditions, contexts and labels for selective execution, and rollback semantics; tracks state in `DATABASECHANGELOG` + `DATABASECHANGELOGLOCK` tables. Use when the user works with Liquibase-managed schemas (Spring Boot heritage, polyglot DB shops), needs cross-DBMS portable migrations, or requires fine-grained rollback control.
Install with skills.sh (any agent)
npx skills add testland/qa --skill liquibase-migrationsliquibase-migrations
Overview
Per github.com/liquibase/liquibase (opens in new window):
"Liquibase helps millions of developers track, version, and deploy database schema changes."
Liquibase's distinguishing feature vs Flyway: changesets are format-portable (XML / YAML / JSON / SQL) and rollback-aware - each changeset declares its own rollback action, enabling deterministic per-changeset rollback rather than versioned-only migration replay.
State tracked in two tables (per Liquibase docs):
When to use
Step 1 - Install
Per lb-gh (opens in new window) the documented quickstart:
# Download installer per platform from liquibase.com/download
# Add liquibase to PATH
liquibase init start-h2 # spin up an H2 dev DB to try it outFor CI / containerized usage:
docker pull liquibase/liquibase:latest
docker run --rm -v "$PWD/changelog:/liquibase/changelog" \
liquibase/liquibase \
--url=jdbc:postgresql://host/db \
--username=user --password=pwd \
--changelog-file=changelog/db.changelog-master.xml \
updateMaven dependency / Gradle plugin available for in-build invocation - consult docs.liquibase.com (opens in new window).
Step 2 - Changelog + changeset basics
A changelog is a file (or set of included files) containing one or more changeset entries. Each changeset has an id + author (these are the identity for the DATABASECHANGELOG row).
YAML example:
databaseChangeLog:
- changeSet:
id: 1
author: alice
changes:
- createTable:
tableName: users
columns:
- column:
name: id
type: bigint
autoIncrement: true
constraints: { primaryKey: true, nullable: false }
- column:
name: email
type: varchar(255)
constraints: { unique: true, nullable: false }
rollback:
- dropTable:
tableName: users
- changeSet:
id: 2
author: alice
changes:
- addColumn:
tableName: users
columns:
- column: { name: created_at, type: timestamp }
rollback:
- dropColumn: { tableName: users, columnName: created_at }Equivalent forms in XML / JSON / SQL exist - pick by team preference; they're functionally equivalent at apply time. (Per lb-docs (opens in new window) for format-specific syntax.)
Step 3 - Core commands
Per lb-gh (opens in new window) and Liquibase command reference:
| Command | Use |
|---|---|
liquibase update | Apply pending changesets |
liquibase status | List pending changesets |
liquibase history | Show executed changesets |
liquibase rollback <tag> | Roll back to a tagged state |
liquibase rollback-count <n> | Roll back the last N changesets |
liquibase tag <tag-name> | Mark current state with a tag (for later rollback) |
liquibase changelog-sync | Mark all pending changesets as applied without running them (legacy adoption) |
liquibase validate | Check changelog syntax + checksums |
Step 4 - Preconditions
Changesets can declare preconditions that must hold before they're applied (or the changeset is skipped / failed):
- changeSet:
id: 3
author: alice
preConditions:
- onFail: MARK_RAN # if precondition fails, mark as run without executing
- onError: MARK_RAN
- tableExists: { tableName: users }
changes:
- addColumn: { tableName: users, columns: [{ column: { name: status, type: varchar(20) }}] }Common precondition predicates: tableExists, columnExists, viewExists, indexExists, foreignKeyConstraintExists, changeSetExecuted, dbms (e.g., only-on-postgresql), primaryKeyExists, sqlCheck (custom SQL returning a single value).
onFail / onError actions: HALT (default - fails the run), CONTINUE, MARK_RAN, WARN.
Step 5 - Contexts and labels
Selective changeset execution per environment / feature:
- changeSet:
id: 4
author: alice
contexts: dev,test # only run when --contexts dev or test
labels: experimental # filterable by --labels selector
changes:
- createTable: { tableName: feature_x_audit, columns: [...] }Run contexts at apply time:
liquibase update --contexts="dev,test"
liquibase update --label-filter="!experimental" # exclude experimentalStep 6 - Include + includeAll for splitting changelogs
Master changelog references file groups (per docs.liquibase.com/concepts/changelogs (opens in new window)):
databaseChangeLog:
- include: { file: changelog/v1.0/users.yaml }
- include: { file: changelog/v1.0/orders.yaml }
- includeAll: { path: changelog/v2.0/, errorIfMissingOrEmpty: true }The includeAll orders files alphabetically; use prefixes (001-users.yaml, 002-orders.yaml) to enforce order.
Step 7 - CI integration
- run: docker run --rm --network=host \
-v "$PWD/changelog:/liquibase/changelog" \
liquibase/liquibase:latest \
--url=jdbc:postgresql://localhost:5432/test \
--username=postgres --password=pwd \
--changelog-file=/liquibase/changelog/db.changelog-master.xml \
update
- run: mvn testPer the testcontainers skill, ephemeral DB + liquibase update is the standard pattern.
Step 8 - Composition with sister tools
Before merge, apply adversarial review of new changesets - classify as additive / breaking / data-loss / locking; estimate downtime.
Anti-patterns
| Anti-pattern | Why it fails | Fix |
|---|---|---|
Reuse a changeset id after it's been deployed | Checksum mismatch on next update; deploys fail | New id per change; never edit deployed changesets |
| Rollback section omitted | liquibase rollback fails silently; downtime | Author rollback per changeset (Step 2) |
Use runAlways: true for migrations | Reruns every deploy; data corruption risk | runAlways is for runInTransaction: false admin scripts only |
| Mix XML + YAML + JSON in one repo | Reviewer cognitive load; sloppy diffs | Pick one format per repo |
| Skip preconditions on conditional changes | Apply fails on environments where preconditions don't hold | Use dbms/tableExists preconditions (Step 4) |
Limitations
References
Related skills
atlas-migrations
Authors and runs Atlas database schema migrations - declarative HCL or SQL schema definition with `atlas schema apply` for desired-state apply OR `atlas migrate diff` to generate versioned migrations against a dev DB; `atlas migrate apply` to deploy; `atlas migrate lint` to flag destructive / locking / data-loss patterns; `atlas migrate hash` to detect tampering. Supports PostgreSQL, MySQL, SQL Server, ClickHouse, SQLite, MariaDB, Snowflake, Oracle, Redshift, Spanner, CockroachDB, Databricks. Use when the user wants Terraform-style declarative DB schema management or modern SQL-first migration linting beyond Flyway / Liquibase.
flyway-migrations
Authors and runs Flyway database migrations - versioned (`V1__add_users.sql`), repeatable (`R__refresh_views.sql`), and undo (`U1__remove_users.sql`) migration files in `db/migration/`; runs `flyway migrate` / `info` / `validate` / `clean` / `baseline` / `repair`; tracks state in the `flyway_schema_history` table; supports 50+ databases including Oracle / SQL Server / MySQL / PostgreSQL / MariaDB / Snowflake / BigQuery; integrates with Maven, Gradle, CLI, and Docker. Use when the user works with Flyway-managed schemas, asks about migration ordering, or needs CI gates on schema changes.
migration-operation-taxonomy
Classifies every DDL and DML statement in a database migration into an eight-category operation taxonomy (additive, backwards-compatible alter, locking, lock-escalating, breaking, data-loss, unsafe default, index-missing foreign key) and assigns a Critical, Warning, or Info severity justified by the lock mode and table-rewrite behavior the target engine actually performs. Records where PostgreSQL and MySQL/InnoDB diverge for the same logical statement, and where behavior is version-gated (PostgreSQL 11 removed the table rewrite for a constant DEFAULT; MySQL 8.0.12 made ADD COLUMN instant). Use when a migration file appears in a diff or a review queue and someone must decide, before it reaches a production-sized table, which statements are safe and which will stall writes or destroy data.
sqlmesh-migrations
Authors and runs SQLMesh - data-transformation framework with version control, virtual data environments, automatic breaking-vs-non-breaking change classification, and downstream impact analysis; supports `sqlmesh init` / `plan` / `apply` / `run` / `audit` / `test` lifecycle; covers DuckDB, Postgres, Snowflake, BigQuery, Redshift, Databricks. Use when the user works with SQL data pipelines (warehouse + dbt-adjacent ELT), needs safer model evolution than dbt's deploy-and-pray, or wants the strongest impact-analysis story in the OSS data tooling space.