Liquibase Cheat Sheet
Overview
Liquibase is an open-source database schema change management tool. It tracks database changes using changelog files that describe changes (changesets) in XML, YAML, JSON, or raw SQL. Each changeset has a unique identifier (author + id) and is tracked in a DATABASECHANGELOG table, ensuring changes are applied exactly once and in order.
Liquibase supports over 50 database platforms including PostgreSQL, MySQL, Oracle, SQL Server, MariaDB, SQLite, H2, and many more. It provides rollback support, diff capabilities, pre-conditions for conditional execution, contexts and labels for environment-specific changes, and integration with CI/CD pipelines. Liquibase can generate changelogs from existing databases and compare schemas across environments.
Installation
# macOS via Homebrew
brew install liquibase
# Linux (Debian/Ubuntu)
curl -L https://github.com/liquibase/liquibase/releases/download/v4.30.0/liquibase-4.30.0.tar.gz | tar xz
sudo mv liquibase /opt/liquibase
sudo ln -s /opt/liquibase/liquibase /usr/local/bin/liquibase
# Docker
docker pull liquibase/liquibase
# Verify installation
liquibase --version
JDBC Drivers
# Drivers are included for common databases
# For additional databases, add the JDBC JAR to:
# $LIQUIBASE_HOME/lib/
# Or specify via CLI
liquibase --classpath=/path/to/driver.jar update
Core Commands
| Command | Description |
|---|---|
liquibase update | Apply pending changesets |
liquibase rollbackCount 1 | Rollback the last N changesets |
liquibase rollbackToDate 2025-01-01 | Rollback to a specific date |
liquibase rollback v1.0 | Rollback to a tag |
liquibase status | Show pending changesets |
liquibase history | Show applied changesets |
liquibase validate | Validate changelog syntax |
liquibase diff | Compare two databases |
liquibase diffChangeLog | Generate changelog from diff |
liquibase generateChangeLog | Generate changelog from existing DB |
liquibase tag v1.0 | Tag the current database state |
liquibase dropAll | Drop all database objects |
liquibase clearCheckSums | Clear stored checksums |
liquibase updateSQL | Preview SQL without executing |
liquibase futureRollbackSQL | Preview rollback SQL |
Changelog Formats
YAML Format
# db/changelog/db.changelog-master.yaml
databaseChangeLog:
- changeSet:
id: 1
author: alice
changes:
- createTable:
tableName: users
columns:
- column:
name: id
type: int
autoIncrement: true
constraints:
primaryKey: true
nullable: false
- column:
name: email
type: varchar(255)
constraints:
unique: true
nullable: false
- column:
name: name
type: varchar(100)
- column:
name: created_at
type: timestamp
defaultValueComputed: CURRENT_TIMESTAMP
- changeSet:
id: 2
author: alice
changes:
- createTable:
tableName: posts
columns:
- column:
name: id
type: int
autoIncrement: true
constraints:
primaryKey: true
- column:
name: title
type: varchar(200)
- column:
name: user_id
type: int
constraints:
foreignKeyName: fk_posts_user
references: users(id)
rollback:
- dropTable:
tableName: posts
SQL Format
-- db/changelog/001-create-users.sql
-- changeset alice:1
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
name VARCHAR(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- rollback DROP TABLE users;
-- changeset alice:2
CREATE INDEX idx_users_email ON users(email);
-- rollback DROP INDEX idx_users_email;
-- changeset alice:3
ALTER TABLE users ADD COLUMN role VARCHAR(20) DEFAULT 'user';
-- rollback ALTER TABLE users DROP COLUMN role;
XML Format
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog
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 id="1" author="alice">
<createTable tableName="users">
<column name="id" type="int" autoIncrement="true">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="email" type="varchar(255)">
<constraints unique="true" nullable="false"/>
</column>
</createTable>
</changeSet>
</databaseChangeLog>
Configuration
liquibase.properties
# liquibase.properties
changeLogFile=db/changelog/db.changelog-master.yaml
url=jdbc:postgresql://localhost:5432/mydb
username=postgres
password=password
driver=org.postgresql.Driver
# Reference database (for diff operations)
referenceUrl=jdbc:postgresql://localhost:5432/mydb_staging
referenceUsername=postgres
referencePassword=password
# Output settings
outputDefaultCatalog=false
outputDefaultSchema=false
logLevel=info
Docker Compose Integration
services:
liquibase:
image: liquibase/liquibase
depends_on:
- db
volumes:
- ./db/changelog:/liquibase/changelog
command: >
--url=jdbc:postgresql://db:5432/mydb
--username=postgres
--password=password
--changeLogFile=changelog/db.changelog-master.yaml
update
Advanced Usage
Preconditions
- changeSet:
id: 5
author: alice
preConditions:
- onFail: MARK_RAN
- not:
- tableExists:
tableName: users
changes:
- createTable:
tableName: users
columns:
- column:
name: id
type: int
Contexts and Labels
# Apply changes based on environment
- changeSet:
id: 10
author: alice
context: dev,test
changes:
- insert:
tableName: users
columns:
- column: { name: email, value: "test@example.com" }
- changeSet:
id: 11
author: alice
labels: "feature-1.2,JIRA-456"
changes:
- addColumn:
tableName: users
columns:
- column: { name: phone, type: varchar(20) }
# Run with context
liquibase --contexts=dev update
# Run with labels
liquibase --label-filter="feature-1.2" update
Include and IncludeAll
# Master changelog that includes others
databaseChangeLog:
- include:
file: db/changelog/001-users.yaml
- include:
file: db/changelog/002-posts.yaml
- includeAll:
path: db/changelog/migrations/
relativeToChangelogFile: true
Custom SQL Changes
- changeSet:
id: 20
author: alice
changes:
- sql:
sql: >
CREATE OR REPLACE FUNCTION update_timestamp()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
- sql:
sql: >
CREATE TRIGGER update_users_timestamp
BEFORE UPDATE ON users
FOR EACH ROW
EXECUTE FUNCTION update_timestamp();
rollback:
- sql:
sql: >
DROP TRIGGER IF EXISTS update_users_timestamp ON users;
DROP FUNCTION IF EXISTS update_timestamp();
CI/CD Integration
# GitHub Actions
name: Database Migration
on:
push:
branches: [main]
paths: ['db/changelog/**']
jobs:
migrate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Liquibase
uses: liquibase/liquibase-github-action@v7
with:
operation: update
classpath: db/changelog
changeLogFile: db.changelog-master.yaml
url: ${{ secrets.DB_URL }}
username: ${{ secrets.DB_USER }}
password: ${{ secrets.DB_PASS }}
Troubleshooting
| Issue | Solution |
|---|---|
| Checksum validation failed | Run liquibase clearCheckSums (note: only if changeset was intentionally modified) |
| Lock stuck on DATABASECHANGELOGLOCK | Run liquibase releaseLocks or manually update the lock table |
| Migration order wrong | Use include with explicit file order in master changelog |
| Rollback not supported | Add explicit rollback blocks to changesets; not all change types auto-generate rollbacks |
| SQL syntax error | Use updateSQL to preview before running; check database-specific syntax |
| Driver not found | Add JDBC driver JAR to lib/ directory or specify --classpath |
| Context not filtering | Ensure --contexts flag is passed; check changeset context attribute |
| Diff generating too many changes | Use diffTypes to limit comparison scope |
| Large changelog slow | Split into multiple files using include and includeAll |