Using Liquibase Contexts to Keep Environment‑Specific Changes in a Single Changelog
Learn how Liquibase contexts let you maintain a single changelog while applying environment‑specific changesets safely, with a concrete example, verification steps, and trade‑offs.
28 Jul 2026, 16:25 UTC

The problem: duplicated changelogs and risky manual edits
When a team maintains separate changelog files for development, test, and production, every schema change must be copied and edited three times. This duplication makes it easy to miss a change, introduce inconsistencies, and lose an audit trail. A single source of truth is preferable, but Liquibase will run every changeset unless you tell it which ones belong to which environment.
How contexts solve the problem
Liquibase context attribute lets you tag each changeset with one or more environment names (e.g., dev, test, prod). At runtime you pass --contexts=<value>; Liquibase executes only the changesets whose context list matches the supplied value. Changesets without a matching context are skipped, while those without any context tag run unconditionally.
Worked example: demo table for dev/test, partitioned index for prod
Create a changelog file db.changelog-master.xml that includes two changesets:
<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-4.0.xsd">
<include file="db.changelog-1.0.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>
---
<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-4.0.xsd">
<changeset id="create-demo-table" author="alice" context="dev,test">
<createTable tableName="demo">
<column name="id" type="BIGINT" autoIncrement="true">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="value" type="VARCHAR(255)"/>
</createTable>
</changeset>
<changeset id="add-partitioned-index" author="bob" context="prod">
<createIndex indexName="idx_demo_partition" tableName="demo">
<column name="value" />
<attributes
PARTITION BY HASH(value)
PARTITIONS 4/
</attributes>
</createIndex>
</changeset>
</databaseChangeLog>
The first changeset creates a simple demo table and is tagged with dev,test. The second adds a partitioned index and is tagged only with prod. No changeset lacks a context tag, so nothing runs unintentionally.
Running the changelog against different environments
Assuming you have an empty H2 database accessible via JDBC URL jdbc:h2:~/testdb and the Liquibase CLI installed:
- Apply the dev/test changesets:
liquibase --url=jdbc:h2:~/testdb \
--changeLogFile=db.changelog-master.xml \
update --contexts=dev,test
You should see Liquibase report that the create-demo-table changeset executed and the add-partitioned-index changeset was skipped. To verify, connect to the H2 console and run:
SHOW TABLES;
Expect to see DEMO listed. Then check for the index:
SELECT INDEX_NAME FROM INFORMATION_SCHEMA.INDEXES WHERE TABLE_NAME = 'DEMO';
No rows should be returned.
- Now apply the prod changeset on the same database:
liquibase --url=jdbc:h2:~/testdb \
--changeLogFile=db.changelog-master.xml \
update --contexts=prod
Liquibase should execute only the add-partitioned-index changeset. Verify again:
SHOW TABLES;
The DEMO table remains unchanged. Then check the index:
SELECT INDEX_NAME FROM INFORMATION_SCHEMA.INDEXES WHERE TABLE_NAME = 'DEMO';
You should see a row with IDX_DEMO_PARTITION (or the exact name you gave).
Trade‑offs and limitations
Contexts introduce indirection: a reader must check the context attribute to know where a changeset will run. Teams should maintain a short README or comment block that lists which contexts are used and what they represent. Additionally, if a changeset omits a context tag and no default --contexts value is supplied, Liquibase will always execute it, potentially promoting dev‑only changes to production. Combining contexts with labels or preconditions requires careful ordering; a mismatched label can cause a changeset to be skipped even when its context matches, leading to drift.
Practical verification checklist
- Run
liquibase update --contexts=<env>against a clean database. - Inspect the
DATABASECHANGELOGtable to confirm which changeset IDs were applied. - Query the schema objects (tables, indexes, etc.) that are expected for that environment.
- Repeat for each environment you support, ensuring no unexpected objects appear.
By following these steps you gain confidence that your single changelog correctly isolates environment‑specific changes while keeping the audit trail intact.
Closing: adopt contexts for cleaner releases
Liquibase contexts let you keep one changelog file, tag each changeset with the environments where it belongs, and apply only the relevant subset at runtime. The approach reduces duplication, simplifies release audits, and—when paired with clear documentation and verification—helps prevent accidental promotion of changes. Start by adding context tags to your existing changesets, test each profile in a staging environment, and make the --contexts argument part of your CI/CD pipeline.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.