CLI icon
Docker icon
If icon
Log icon
SlackIncomingWebhook icon
Pause icon
Fail icon
Schedule icon

Release a Stuck Liquibase Changelog Lock Safely

Safely release an orphaned Liquibase changelog lock after approval and verify the database can plan pending SQL.

Categories
DataInfrastructure

Safety

A lock left by a killed migration blocks every later deployment. Releasing a lock while its migration is still running can corrupt DATABASECHANGELOG, so this flow only proposes a lock older than the configured grace period and pauses before releaseLocks.

Guard What it stops
stale_after_seconds A live migration whose lock is still within its normal runtime
Snapshot comparison after Pause Acting on a holder, timestamp or pending set that changed while approval was waiting
auto_remediate plus dry_run Writes from the shipped defaults and disabled schedule
Empty-lock and updateSQL verification Calling a successful CLI exit a recovery when the lock remains or SQL cannot be planned

How it works

  1. inspect_before (io.kestra.plugin.liquibase.CLI) runs listLocks and status --verbose against the target changelog. It records the holder, granted timestamp, age, pending count, exit codes and a snapshot hash.
  2. findings (io.kestra.plugin.core.flow.If) logs an empty table, a legitimate young lock or a stale candidate. A healthy database and a migration still inside the grace period are spared.
  3. remediation_mode requires auto_remediate: true and dry_run: false. It posts the stale age and pending count to Slack then waits at approval_gate (io.kestra.plugin.core.flow.Pause).
  4. recheck_before_write repeats both CLI reads after approval. stale_plan_guard compares its snapshot with the original and fails before any write when the target changed.
  5. release_lock (io.kestra.plugin.liquibase.CLI) runs Liquibase releaseLocks for the same changelog. It is the only mutating task.
  6. verify_release reads listLocks and status again then runs updateSQL. verify_result fails unless the lock table is empty and all reads plus SQL planning succeed.
  7. The disabled every_15_minutes schedule forces the safe inputs. The errors block sends a Slack alert for any failed detection, safety gate, write or verification.

What you get

  • The lock holder and granted time that explain which migration blocked the release.
  • A stale-lock decision based on a bounded age rather than a blind force-unlock.
  • A human approval record and a second eligibility read immediately before the write.
  • Proof from Liquibase itself that the lock table is empty and the pending changesets can be planned.
  • A repeatable read-only scheduled check with no write enabled by default.

Who it's for

  • Platform teams whose migration pods can be killed during deploys.
  • Release engineers who need to unblock a queue without editing DATABASECHANGELOGLOCK by hand.
  • Database owners who require a visible approval and a post-change proof before the next deploy.

Why orchestrate this with Kestra

A shell alias can run releaseLocks, but it cannot preserve the pre-approval snapshot, refuse a changed target, record who approved the write or fail when the database still cannot plan SQL. Kestra keeps the CLI reads, approval, guarded write and independent verification in one execution history.

Tested end to end

Tested against Kestra 2.0.5, Liquibase 4.29.2 and PostgreSQL 16 in a Docker test fixture. The fixture seeds two changesets, forces DATABASECHANGELOGLOCK with a one-hour-old holder and leaves one changeset pending. The remediation path measures one held lock before, zero locks after, one pending changeset before and a successful updateSQL plan after. Healthy, dry-run, declined, ineffective, repeated and approval-race cases are recorded in the evidence manifest for this blueprint.

Case Execution Result
Healthy 7JEW2E3aa3P61CX54UBzCL SUCCESS. No lock held, pending changeset read, nothing written
Dry run 79XuWzgJSIhfxR2bjXyWl3 SUCCESS. One-hour-old lock found, lock count still 1
Declined approval 494kvhHyzeGcfuzu19S272 SUCCESS. Decline logged, lock count still 1
Remediation APusFXL4laAM0qRGk31YA SUCCESS. Lock count 1 to 0, updateSQL plans the pending changeset, applied changesets kept
Repeat 5BPm8aKrISTmoc2CcoJg6G SUCCESS. Lock table already empty, no write
Release refused 3wVzdDW30A698E9fKG421Z FAILED as intended. Verification gate stopped the run, lock count still 1
Holder changed during the Pause 5bCbriKDgneQcxwAVpWUEL FAILED as intended. New holder detected before releaseLocks, lock count still 1

Prerequisites

  • A PostgreSQL or other Liquibase-supported database reachable by the Kestra worker.
  • A changelog whose DATABASECHANGELOGLOCK row is managed by Liquibase. Replace the example changelog_yaml variable with the changelog used by the deployment.
  • A worker with the Liquibase CLI plugin and permission to read the changelog tables plus execute releaseLocks.

Secrets

  • LIQUIBASE_JDBC_URL: JDBC URL for the target database.
  • LIQUIBASE_USERNAME: Liquibase database user.
  • LIQUIBASE_PASSWORD: Liquibase database password.
  • SLACK_WEBHOOK_URL: Slack incoming webhook for candidate and failure alerts.

Inputs

Input Default Purpose
stale_after_seconds 900 Minimum lock age before a candidate is proposed
auto_remediate false Enables the approval and write branch
dry_run true Keeps the run read-only when true
docker_network bridge Docker network used by the worker task runner

Quick start

  1. Add the four secrets and replace the example changelog_yaml with the production changelog.
  2. Run with the defaults. Confirm the lock holder, age and pending count in inspect_before.
  3. For a confirmed orphan, run with auto_remediate: true and dry_run: false, inspect the Slack alert, then resume the Pause with approved: true.
  4. Treat a failed verification as unresolved. The execution contains the lock and updateSQL output needed for the next investigation.
  5. Enable every_15_minutes only after confirming its forced safe-mode inputs are appropriate for your namespace.

How to extend

  • Load the changelog from namespace files or a Git checkout while keeping the same file name passed to every CLI task.
  • Set stale_after_seconds from the longest observed migration plus a review margin.
  • Add a deployment identifier to the changelog and the Slack payload so the on-call can correlate the lock with release logs.
  • Chain this guard before the normal migration flow and stop the release when verification fails.

Limits: lock age is parsed from Liquibase's human-readable listLocks output, so a locale or CLI format change that cannot be parsed fails closed and does not release anything. The flow does not prove that a discarded migration left no partial external side effects; inspect the failed migration before approving.

Links

This blueprint was created by zkasuran.

See How

New to Kestra?

Use blueprints to kickstart your first workflows.