Schema migrations in 2026: revisit Atlas, Flyway and Liquibase before you ship another ALTER

Picture a retail SaaS company in Coimbatore that runs order management for textile wholesalers across Tamil Nadu (an invented example, not a real client). About thirty engineers, one PostgreSQL 17 cluster, a Java order service, a few Go and Python services around it, and a GitLab pipeline that deploys several times a week. Finance has asked for a gstin column on orders so invoices can carry the buyer’s GST number, plus an index so support staff can search by it. A two-line change. The architecture review still runs for an hour, because it opens an older question: how should this team change its schema from now on? One engineer wants Atlas, because “schema as code, like Terraform”. The Java lead wants Flyway, because “plain SQL files, already works with Spring Boot”. The senior DBA wants Liquibase, because “proper changelog, rollbacks, and we may add SQL Server for a client”. Legal, copied on the thread, asks a fourth question nobody had prepared for: which of these is actually open source today?

These three tools do overlap, but they are not three wrappers around the same idea. Atlas is a schema management tool that can work declaratively: you describe the schema you want in HCL, SQL or an ORM model, and Atlas inspects the database, computes a plan and applies it; it can also generate classic versioned migration files from that desired state. Flyway is a versioned migration runner: you write ordered SQL (or Java) migrations named V1__..., V2__..., and Flyway applies the pending ones exactly once, recording each in a history table. Liquibase is a changelog-based tool: changesets in SQL, XML, YAML or JSON, each identified by id, author and file, tracked in its own tables, with automatic rollback for some change types. This post compares where they stand on 14 October 2026: models, editions and licences, rollbacks, CI behaviour, PostgreSQL specifics, security advisories and a decision guide. Facts come from GitHub releases, tags and LICENSE files, the projects’ own documentation, and advisory records cross-checked against NVD and cve.org. The drill near the end is my own run of all three against a throwaway PostgreSQL 17.11, using the same invented change. I used the Atlas community build, the Flyway OSS CLI and Liquibase Community; I did not run any paid edition, and I say so wherever it matters.

The short version

  • Decide first what problem you have. A new service with no migration history can choose freely, and Atlas’s declarative model is a real option. A service with years of applied migrations usually keeps its history tool, and the question is how to make it safer. A product that ships to several database engines leans towards Liquibase’s modelled changelogs or Flyway’s per-database SQL, with very different trade-offs.
  • Atlas (ariga/atlas) is at v1.3.0 (GitHub release 2 August 2026, 2:39 PM IST). The source and the community build are Apache 2.0. The default binary you get from curl -sSf https://atlasgo.sh | sh is distributed under Ariga’s MSA, and Pro features unlock after atlas login. The community build lacks linting, schema plan, migrate down, views, functions, triggers, sequences and several drivers, according to Atlas’s own Community Edition page (my drill found that basic migrate lint still ran; see step 5).
  • Flyway (flyway/flyway) is at 13.10.0 (8 October 2026, 3:05 PM IST). The open-source CLI from GitHub is Apache 2.0 and prints “Flyway OSS Edition”. Undo migrations and dry-run scripts belong to Redgate’s Teams level or above; drift detection and script generation belong to Enterprise.
  • Liquibase (liquibase/liquibase) Community is at v5.0.4 (20 August 2026, 9:48 PM IST). Since 5.0.0 (30 September 2025) the LICENSE at the tag is the Functional Source License (FSL-1.1-ALv2), a Fair Source licence that turns into Apache 2.0 two years after each release. v4.33.0 (9 July 2025) is the last Apache 2.0 release. The commercial edition is Liquibase Secure, described by Liquibase as the enterprise evolution of Liquibase Pro.
  • Security history is small. Liquibase has one verified CVE for the core library, CVE-2022-0839 (XML external entity, fixed in 4.8.0). I found no matching advisory for Atlas or Flyway in the sources checked on 14 October 2026. Liquibase 5.0.3 and 5.0.4 also fixed reported issues that carry no CVE ID in their notes.
  • On PostgreSQL, the hardest part is not the tool. It is locks, transactions and CREATE INDEX CONCURRENTLY. In my drill, each tool needed a different setting before a concurrent index went through cleanly, and Flyway’s default lock left a half-applied migration when I killed a hung run.

Where things stand on 14 October 2026

Dates are GitHub release published_at, converted from UTC to IST (UTC+5:30). Licences are from the LICENSE files at the tagged releases, read together with each project’s edition notes. None of the listed releases is marked prerelease.

Project

Release

Date (IST)

Notes

Licence

Atlas (ariga/atlas)

v1.3.0

2 August 2026, 2:39 PM

Current release; announcement post dated 30 July 2026; security graph in Atlas Cloud, Atlas Scripts, atlas cloud commands, pre-apply drift detection

Source and community build Apache 2.0; default binary under Atlas MSA

Atlas

v1.2.0

10 April 2026, 6:06 PM

Schema ownership policy, offline access; tags v1.2.1 to v1.2.3 (May and June 2026) exist without GitHub release pages

Same split

Atlas

v1.0.0

25 December 2025, 1:42 AM

First 1.x release

Same split

Flyway (flyway/flyway)

13.10.0

8 October 2026, 3:05 PM

Current release; deprecations of checkDriftOnMigrate and flywayServicePublish.publishReport

Apache 2.0 (OSS edition)

Flyway

13.9.0

1 October 2026, 5:44 PM

System proxy use made opt-in again

Apache 2.0

Flyway

13.0.0

20 July 2026, 3:36 PM

Start of the 13 line; Flyway MCP server (an Enterprise feature, per Redgate’s product page)

Apache 2.0

Liquibase Community (liquibase/liquibase)

v5.0.4

20 August 2026, 9:48 PM

Current release; credential redaction, opt-in lockdown flags, DOCTYPE rejected under secure parsing, status servlet XSS fix

FSL-1.1-ALv2

Liquibase Community

v5.0.3

15 May 2026, 5:37 PM

Input sanitisation for generate-changelog closing two reported issues

FSL-1.1-ALv2

Liquibase Community

v5.0.0

30 September 2025, 7:19 PM

FSL licence, ships without drivers and extensions (installed through LPM), Java 17 minimum

FSL-1.1-ALv2

Liquibase

v4.33.0

9 July 2025, 8:08 PM

Last 4.x release

Apache 2.0

Practical notes on the table. Flyway ships often: twelve releases from 13.0.0 to 13.10.0 in under three months, so pin an exact version in CI and in your build file. Liquibase moved to a quarterly community release plan from 5.0.2 (February, May, August, November) with nightly builds in between; the next one is due in November 2026 by that plan. Atlas has shipped a minor release every two to four months since v1.0, and its download server also publishes canary builds: on fetch day the community “latest” pointer named a v1.3.1 canary build, so I pinned v1.3.0 by name. GitHub stars on fetch day were about 10,145 (Flyway), 8,772 (Atlas) and 5,626 (Liquibase); stars measure attention, not fitness.

Timeline

Date (IST)

Event

9 July 2011

liquibase/liquibase repository created on GitHub

15 December 2012

flyway/flyway repository created on GitHub

24 July 2019

Flyway creator Axel Fontaine announces Redgate as the new steward of Flyway

1 May 2021

ariga/atlas repository created on GitHub

4 March 2022

CVE-2022-0839 (Liquibase XXE) published; fixed in 4.8.0

31 October 2023

Flyway 10.0.0

21 November 2024

Flyway 11.0.0 (date from Redgate’s release notes)

9 July 2025

Liquibase 4.33.0, the last Apache 2.0 release

30 September 2025

Liquibase 5.0.0 under FSL; Liquibase Secure announced as the evolution of Liquibase Pro

25 December 2025

Atlas v1.0.0

28 January 2026

Flyway 12.0.0

5 February 2026

Atlas v1.1.0 (security as code, declarative data, CockroachDB and Aurora DSQL drivers)

5 March 2026

Liquibase 5.0.2, first release of the quarterly cadence

20 July 2026

Flyway 13.0.0

2 August 2026

Atlas v1.3.0

20 August 2026

Liquibase 5.0.4

8 October 2026

Flyway 13.10.0

Liquibase and Flyway are both more than a decade old; Atlas is five years old and reached 1.0 less than a year ago.

First question: what problem are you solving?

Before comparing features, the Coimbatore review has to agree on which of three situations it is in.

Greenfield, one engine, schema owned by the team. A new service with no applied history can pick any model. Here Atlas’s declarative workflow is attractive: the schema file is the single source of truth, the plan is computed, and reviews look at both the desired schema and the generated SQL. Flyway and Liquibase still work fine; you write the change yourself.

Existing history. If a database already has two hundred applied Flyway migrations, or a Liquibase changelog that has run in production for years, the history table is a contract. Every environment’s state is defined by “which files have run”. Switching tools is possible (Atlas has migrate import for Flyway, Liquibase, golang-migrate, goose and dbmate directories, and every tool has some form of baseline), but it is a project, not a weekend. Most teams in this situation get more value from better linting, safer locking and stricter CI than from a new tool.

Many engines from one source. If the product must create the same schema on PostgreSQL, SQL Server and Oracle, Liquibase’s modelled changelogs (XML, YAML, JSON change types that Liquibase translates per database) are the traditional answer. Flyway’s answer is per-database SQL folders. Atlas supports many engines too, but in 2026 most non-open-source drivers (SQL Server, Oracle, ClickHouse, Snowflake and others) are Pro features.

The order service in our example is in the second situation: it already has a Flyway folder with about ninety files. The new analytics service is in the first. That split decides more than any feature table.

What each project actually is

Atlas v1.3.0

Atlas, from Ariga, calls itself a language-agnostic tool for managing and migrating database schemas. Its README describes two workflows. In the declarative workflow, atlas schema apply compares the live database with a desired state written in HCL, SQL or an ORM schema (the README lists 16 ORM loaders across six languages), plans the difference and applies it after approval. In the versioned workflow, atlas migrate diff writes a new SQL migration file from the same desired state, and atlas migrate apply runs pending files in order. Both workflows need a dev database: a temporary, empty database of the same engine and version, used to normalise and validate the desired schema. Atlas can start one in Docker (docker://postgres/17/dev) or use one you provide.

Versioned migrations carry an atlas.sum file, a hash of every migration file plus a sum of the whole directory, so two branches that each add a migration produce a conflict in Git instead of a surprise in production. Applied versions are recorded in an atlas_schema_revisions table.

The important nuance is editions. Atlas’s Features page calls it an open-core project. The repository and the community build are Apache 2.0. The standard binary is free to download under the Atlas MSA and unlocks Pro features after atlas login; new users get a 30-day trial, after which a licence is needed. The Community Edition page lists what the community build does not include: migrate lint, schema lint, schema plan, schema validate, migrate down, checkpoint, rebase, edit, rm, push and test; pre-migration checks, hooks, approval policies and drift detection; views, triggers, functions, procedures, materialised views, sequences, extensions, partitioning and row-level security; drivers for SQL Server, ClickHouse, Redshift, Oracle, Spanner, Snowflake, Databricks, CockroachDB, YugabyteDB, Aurora DSQL and Azure Fabric; and the Kubernetes operator, Terraform provider, GitHub Action, IDE plugins and Go SDK. For a plain PostgreSQL, MySQL, MariaDB or SQLite schema of tables, columns, indexes, constraints and enums, the community build is usable. For anything beyond that, plan on the MSA binary.

Flyway 13.10.0

Flyway started as Axel Fontaine’s open-source project and has been stewarded by Redgate since 2019. Its model is simple and has hardly changed: migrations are files with a version and a description, V1__create_orders.sql, V2__add_gstin.sql, applied in version order exactly once. Repeatable migrations (R__orders_view.sql) have no version and are re-applied whenever their checksum changes, always after the versioned ones in a run; they suit views and functions written with CREATE OR REPLACE. Flyway records each migration in flyway_schema_history with a checksum (CRC32 for SQL migrations), so validate fails if someone edits a file that already ran. Other core commands are info, migrate, repair, baseline and clean, and clean is disabled by default through cleanDisabled=true.

Editions need care. The GitHub release tarball is the open-source CLI under Apache 2.0, and it identifies itself as “Flyway OSS Edition”. Redgate also ships a free Flyway Community edition with a desktop GUI, plus Teams and Enterprise; the README says these Redgate editions “are not part of the Flyway open source project”. Redgate’s feature summary marks undo migrations, dry run scripts, cherry-picking and state-based projects as Teams or above, and drift detection, changes reports, script generation and custom code analysis rules as Enterprise. Callbacks are “limited” in Community. Much of the current release-notes traffic (MCP server, Docker provisioners, Flyway Pipelines, comparison engine upgrades) concerns the Redgate editions, so read release notes with that filter.

Liquibase Community 5.0.4

Liquibase tracks a changelog: an ordered list of changesets, each identified by id, author and the changelog file path. Changelogs can be formatted SQL, or modelled XML, YAML or JSON, where you write change types such as createTable, addColumn or createIndex and Liquibase generates database-specific SQL. Liquibase records applied changesets in DATABASECHANGELOG, with a checksum per changeset, and serialises runs through a one-row DATABASECHANGELOGLOCK table. Contexts, labels and preconditions decide which changesets run where. For many modelled change types (createTable, addColumn, renameColumn and others) Liquibase can generate the rollback automatically; for dropTable, insert and every formatted SQL changeset you must write the rollback yourself. Community supports rollback to a tag, rollback-to-date, rollback-count, their *-sql preview helpers, update-testing-rollback and future-rollback-sql.

The 5.0 release changed three things that matter to a platform team. First, the licence: Liquibase Community 5.x is under FSL-1.1-ALv2, which allows any purpose except a “Competing Use” (internal use, non-commercial education and research, and professional services are named explicitly), and grants Apache 2.0 on the second anniversary of each release. Fair.io, which promotes the Fair Source label, describes it as a way of sharing code “without confusing this with Free and Open Source Software”. So 5.0.4 becomes Apache 2.0 on 20 August 2028; until then it is source-available, not open source in the usual sense, even though Liquibase’s own release notes still say “open source Community”. Contributors now sign a CLA. Second, packaging: Community 5.x ships without drivers and extensions, and the integrated Liquibase Package Manager (liquibase lpm) installs them. Third, Java 17 is the minimum. The commercial edition, Liquibase Secure, adds policy checks, drift detection, --rollback-on-error and commercial support.

Comparison tables

Model and state

Question

Atlas

Flyway

Liquibase

Primary model

Desired state (declarative) or generated versioned files

Ordered versioned SQL or Java migrations

Changelog of changesets (SQL or modelled XML, YAML, JSON)

Who writes the DDL

Atlas plans it; you review (or edit) the SQL

You write it

You write it (SQL) or Liquibase generates it from change types

Where history lives

Declarative: none in the target; versioned: atlas_schema_revisions

flyway_schema_history

DATABASECHANGELOG and DATABASECHANGELOGLOCK

Edit protection

atlas.sum directory hash

Per-file checksum in history

Per-changeset checksum in history

Repeatable objects

No repeatable migrations (Atlas docs say so)

R__ files re-run on checksum change

runOnChange and runAlways attributes

Existing database

schema inspect, baseline file, migrate import, --baseline

baseline command and baseline migrations

generate-changelog, changelog-sync

Licence of what I ran

Apache 2.0 (community build)

Apache 2.0 (OSS CLI)

FSL-1.1-ALv2 (Community 5.0.4)

Workflows and CI behaviour

Need

Atlas

Flyway

Liquibase

Preview SQL before apply

schema apply --dry-run; migrate apply --dry-run

Dry run script is Teams+; OSS shows info only

update-sql, rollback-*-sql in Community

Lint for dangerous changes

migrate lint with analysers (Pro per docs)

SQLFluff integration; custom rules Enterprise

Policy checks are Secure

Block edited history

Yes, checksum mismatch

Yes, validate

Yes, validation on update

Drift detection

Pro (v1.3 added pre-apply drift checks)

Enterprise

Secure

Kubernetes and GitOps

Kubernetes operator and Terraform provider (not in the community build)

Docker image, run as a Job

Docker image, run as a Job

Spring Boot

No built-in Spring Boot integration

Built in (spring.flyway.* properties)

Built in (spring.liquibase.* properties)

Rollbacks, honestly labelled

Tool

What exists

What it really does

Atlas

migrate down (not in the community build)

Computes a reverse plan from the current database state with pre-migration checks, rather than replaying a hand-written down file

Flyway

undo with U files (Teams+)

Runs your hand-written undo script for a version; Redgate’s docs warn that undo assumes the whole migration succeeded

Liquibase

rollback, rollback-count, rollback-to-date (Community)

Runs generated inverses for supported change types or your written rollback; an addColumn inverse is DROP COLUMN, with the data in it

In production most teams roll forward: a new migration that fixes the previous one. Rollback commands are useful in development and in the first minutes after a failed deploy, and dangerous after data has been written. Write that down in the runbook before anyone needs it.

Symptom to shape

You notice

Usually means

Reasonable direction

Reviews argue about the final schema, not the steps

You want a desired-state source of truth

Atlas declarative, or Atlas-generated versioned files

Engineers hand-write the same ALTER patterns and miss lock issues

You need linting and review, not a new runner

Atlas lint (Pro), SQLFluff, a CI dev database

A product must install on PostgreSQL and SQL Server

One logical change, several dialects

Liquibase modelled changelogs, or Flyway per-database folders

Legal asks for OSI-approved licences only

Edition and licence matter more than features

Flyway OSS, Atlas community build, Liquibase 4.33.0 or earlier

“Who ran that hotfix?” comes up monthly

Drift and access control problem

Remove direct DDL rights; drift detection is a paid feature in all three

The same change three ways

The invented change: create app.orders (id, status, created_at), then add gstin varchar(15) and an index on it. These are trimmed from the drill files.

Atlas, desired state in HCL (version 2 of the file). Atlas plans ALTER TABLE ... ADD COLUMN and CREATE INDEX from the difference:

table "orders" {
  schema = schema.app
  column "id" {
    type = bigint
    identity {
      generated = ALWAYS
    }
  }
  column "status" {
    type    = text
    default = "new"
  }
  column "created_at" {
    type    = timestamptz
    default = sql("now()")
  }
  column "gstin" {
    type = varchar(15)
    null = true
  }
  primary_key {
    columns = [column.id]
  }
  index "orders_gstin_idx" {
    columns = [column.gstin]
  }
}

Flyway, two ordered files plus one setting. The concurrent index needs its own file and, on PostgreSQL, a session-level lock:

-- sql/V2__add_gstin.sql
ALTER TABLE app.orders ADD COLUMN gstin varchar(15);

-- sql/V3__index_gstin.sql
CREATE INDEX CONCURRENTLY orders_gstin_idx ON app.orders (gstin);

-- flyway.toml (excerpt)
-- [flyway.postgresql]
-- transactional.lock = false

Liquibase, a YAML changeset for the column and a formatted SQL changeset for the concurrent index:

  - changeSet:
      id: add-gstin
      author: drill40
      changes:
        - addColumn:
            schemaName: app
            tableName: orders
            columns:
              - column:
                  name: gstin
                  type: varchar(15)
--liquibase formatted sql

--changeset drill40:index-status-concurrently runInTransaction:false
CREATE INDEX CONCURRENTLY orders_status_idx ON app.orders (status);
--rollback DROP INDEX CONCURRENTLY app.orders_status_idx;

Two details are easy to miss. Atlas HCL columns are NOT NULL unless you write null = true, so a forgotten line changes the meaning. Liquibase’s autoIncrement: true became GENERATED BY DEFAULT AS IDENTITY on PostgreSQL, while my Atlas and Flyway versions used GENERATED ALWAYS: the same intent, different behaviour when someone inserts an explicit id. Whatever tool you use, read the SQL it actually runs.

PostgreSQL notes none of them can hide

All three tools send DDL to PostgreSQL. PostgreSQL decides the locks.

Most ALTER TABLE forms take an ACCESS EXCLUSIVE lock. The PostgreSQL 17 documentation says so directly (“unless explicitly noted”). Adding a nullable column, or a column with a non-volatile default, does not rewrite the table: the default is stored in metadata. Adding a column with a volatile default, or changing a column’s type, rewrites the table and its indexes. The lock itself is short for metadata-only changes, but it queues behind any long-running query on the table, and every new query queues behind it. Set lock_timeout for migration sessions so a blocked ALTER fails fast instead of freezing the order screen. Note the PostgreSQL docs’ warning that a lock_timeout equal to or larger than statement_timeout is pointless.

CREATE INDEX CONCURRENTLY cannot run inside a transaction block. It also waits for every existing transaction that could use or modify the index, scans the table twice, and on failure leaves an “invalid” index behind. Every tool wraps migrations in a transaction by default, so every tool needs an escape hatch: --tx-mode none for Atlas, a separate non-transactional file plus postgresql.transactional.lock=false for Flyway, and runInTransaction:false for Liquibase. My drill hit all three cases.

The migration’s own lock can be the blocker. Flyway’s default on PostgreSQL is a transactional advisory lock held on a separate connection. Flyway’s PostgreSQL page warns that this “can cause issues with certain SQL statements, most notably CREATE INDEX CONCURRENTLY”. In the drill, the concurrent index waited on that lock-holding transaction and the run hung. Liquibase’s lock is a committed row flag, so it did not block the concurrent build; its failure mode is a stale lock row after a crash.

Connection poolers and HA. Run migrations on a direct connection to the primary, not through a transaction-mode pooler; advisory locks and session settings do not survive transaction pooling. The PgBouncer post covers the pooling modes. After a failover managed by Patroni or CloudNativePG, a half-finished non-transactional migration is still half-finished; see the PostgreSQL HA post for how the primary moves.

Keep server upgrades out of the migration window. A PostgreSQL major version upgrade is not a schema migration, even though teams often schedule the two together. Upgrade the server, check its settings and performance, and only then ship schema changes. If the upgrade is to PostgreSQL 18, the PostgreSQL asynchronous I/O post covers the io_method settings worth reviewing first.

Downstream consumers see your DDL. A new column flows into CDC pipelines and data jobs. If you stream orders with Debezium, plan the change with the consumers; the change data capture post shows how a gstin column appeared in events without a restart. Batch jobs scheduled in tools from the data pipeline orchestration post often select columns by position or by a fixed list, and break more quietly.

Distributed SQL is a different conversation. Online schema changes on CockroachDB, YugabyteDB or TiDB have their own rules and their own background jobs. Atlas and Flyway both list some of these engines (for Atlas, mostly in Pro), but test there separately; the distributed SQL post explains why the same DDL behaves differently.

Security (verified advisories)

I checked each project’s GitHub repository security advisories, the GitHub global advisory database by package, OSV, NVD keyword and CVE-ID searches, and cve.org, on 14 October 2026.

Atlas

No repository advisories for ariga/atlas, no GitHub global advisories or OSV entries for the ariga.io/atlas Go modules, and NVD keyword searches for “atlasgo” and “ariga.io” returned zero results. A search for “ariga” returned only an unrelated WordPress plug-in. No matching advisory found in sources checked on 14 October 2026.

Flyway

No repository advisories for flyway/flyway, no GitHub global advisories or OSV entries for org.flywaydb:flyway-core, flyway-commandline or flyway-database-postgresql, and NVD keyword searches for “flyway” and “Redgate Flyway” returned zero. No matching advisory found in sources checked on 14 October 2026. The 13.0.0 release notes do record a dependency update of jackson-databind “to address a High-severity Incorrect Authorization advisory”; no CVE is named there, so I do not name one.

Liquibase

ID

Affects

Fixed in

Scores

What it was

CVE-2022-0839 / GHSA-jvfv-hrrc-6q72

org.liquibase:liquibase-core below 4.8.0

4.8.0

NVD CVSS 3.1: 9.8 critical; huntr CVSS 3.0: 7.3 high

Improper restriction of XML external entity reference in changelog parsing

The CVE has NVD totalResults = 1, a cve.org record and a CVE API record listing liquibase below 4.8.0 as affected. NVD’s keyword search for “liquibase” also returns CVE-2018-1000146 and CVE-2020-2283, CVE-2020-2284 and CVE-2020-2285; those concern the Jenkins Liquibase Runner plug-in, a separate Jenkins plug-in, not Liquibase itself. Liquibase’s own notes add two items without CVE IDs: 5.0.3 says its generate-changelog input sanitisation “closes two reported vulnerabilities”, and 5.0.4 fixed a reflected cross-site scripting issue in the embedded status servlet. 5.0.4 also rotated the release signing key (“GPG verification requires Liquibase Community 5.0.4 or later”) and, with liquibase.secureParsing=true (the default), rejects XML changelogs that carry a DOCTYPE preamble.

Shared controls

  • A migration tool executes whatever is in the repository with DDL rights. Treat migration files as code: review, signed commits, protected branches.
  • Liquibase 5.0.4’s new lockdown flags (allowExecuteCommand, allowCustomChange, allowSqlPrecondition, allowExternalChangelogPaths and others) all default to true. Set them to false if changelogs ever come from less-trusted sources.
  • Give the migration role ownership of the application schema and nothing more: no superuser, no CREATEDB unless needed. In the drill the migrator role could not create databases, and that was correct.
  • Keep passwords out of files. In the drill, Flyway’s TOML referenced ${env.FW_PASS} and Liquibase took LIQUIBASE_COMMAND_PASSWORD from the environment (the throwaway server used trust authentication, so this shows the pattern, not a password check). The Vault and OpenBao post covers short-lived database credentials.
  • Pin and verify downloads. All three publish checksums, and Liquibase’s LPM verifies driver checksums itself.

Decision guide

Situation

Lean towards

Watch out for

New PostgreSQL service, tables and indexes only, open-source licence required

Atlas community build (declarative or versioned)

Lint not included per the docs, no views or functions, no schema plan in community

New service that wants linting, drift and GitOps operators

Atlas standard binary with Pro

Atlas MSA and a paid licence after the trial

Existing Flyway history, mostly PostgreSQL or MySQL

Stay on Flyway OSS; add CI validation and a lint step

Undo and dry run are not in OSS; tune PostgreSQL locking for concurrent indexes

Existing Liquibase 4.x history, OSI licence policy

Stay on 4.33.0 for now, plan the FSL review

No further Apache 2.0 releases; security fixes land in 5.x

Multi-database product, one logical change for many engines

Liquibase modelled changelogs

FSL terms for 5.x; generated SQL differs per engine

Java monolith with Spring Boot already running Flyway or Liquibase on start-up

Keep the tool; move migration to a separate pipeline step

Every pod waits on the migration lock at start-up, and a stale lock blocks them all

Regulated estate wanting approvals, audit and drift reports

Paid editions (Atlas Pro, Flyway Enterprise, Liquibase Secure)

Compare on your own estate; vendor claims are the vendor’s own

For the Coimbatore team, my recommendation would be: keep Flyway OSS for the order service (ninety files of history are a contract), add validate and a dev-database run in CI, and set the PostgreSQL lock option for concurrent index files. For the new analytics service, try the Atlas community build in versioned mode, so engineers write the desired schema and still commit plain SQL files with atlas.sum. Do not adopt Liquibase 5.x until legal has read the FSL; if a SQL Server client really arrives, revisit that decision with the licence answer in hand.

Checklist

  1. Write down which situation you are in: greenfield, existing history, or many engines.
  2. Record the edition and licence of the exact binary you run (community or MSA build, OSS or Redgate edition, Liquibase 4.x or 5.x), not just the project name.
  3. Pin the tool version in CI, the Docker image tag and the build file; verify the checksum on download.
  4. Run every migration against a disposable database of the same PostgreSQL major version in CI.
  5. Fail CI on checksum mismatches (atlas migrate validate, flyway validate, Liquibase validation on update).
  6. Set lock_timeout for migration sessions, and treat CREATE INDEX CONCURRENTLY as its own non-transactional step.
  7. Run migrations as a separate deploy step, once, not from every application pod on start-up.
  8. Give the migration role schema ownership only, and remove direct DDL from humans in production.
  9. Write the rollback policy: roll forward by default, and say exactly when a rollback command may be used.
  10. After each deploy, compare the live schema with the expected one (atlas schema inspect, flyway info, Liquibase status) and alert on surprises.

Common mistakes

  1. Calling all three tools “open source” without checking the binary: Atlas’s default binary is under the Atlas MSA, and Liquibase 5.x is under a Fair Source licence.
  2. Editing an applied migration file “just to fix a comment”, then running repair or re-hashing in production to make the error go away.
  3. Putting ALTER TABLE and CREATE INDEX CONCURRENTLY in one migration and being surprised by the error, or the hang.
  4. Letting a declarative tool run with --auto-approve against a database where people still make hand fixes; the plan will drop what it does not know.
  5. Generating migration files from a script in a loop, so two files share a timestamp version.
  6. Trusting automatic rollback of addColumn after real data has been written into that column.
  7. Running migrations through a transaction-mode connection pooler.
  8. Forgetting that Liquibase identifies a changeset by file path as well as id and author, then moving the changelog file.
  9. Leaving clean enabled anywhere near production, or copying a development flyway.toml that turns cleanDisabled off.
  10. Choosing a tool on the strength of a vendor’s percentage claims instead of a one-day trial on your own schema.

A drill I ran on 14 October 2026

Environment: shared Linux VM (x86_64), no Docker, no Kubernetes. A new throwaway PostgreSQL 17.11 data directory on 127.0.0.1:15440 (server binaries reused from the previous drill, trust authentication on loopback because this was a disposable server), role migrator with no superuser and no CREATEDB, and one database per tool, each with schema app. Port 8766 was left alone for the existing static file server. Times: about 6:25 PM to 6:34 PM IST. Artefacts are under /workspace/revisit/drill40/.

Tools, all checksum-verified against published values: Atlas community v1.3.0 (sha256 from release.ariga.io), Flyway OSS 13.10.0 linux-x64 tarball from GitHub (sha256 from the release asset; it bundles a JRE and PostgreSQL JDBC 42.7.12), and Liquibase Community 5.0.4 (sha256 from the release asset) on JDK 21. Liquibase ships no PostgreSQL driver: the first liquibase lpm call downloaded LPM 0.3.7 (I verified that zip separately), and liquibase lpm add postgresql installed postgresql-42.7.13.jar into the project’s liquibase_libs/ folder after printing “Checksum verified”.

What I did:

  1. Atlas, declarative. schema apply --dry-run on version 1 of the HCL file planned CREATE TABLE "orders" with NOT NULL columns; applying it worked. Version 2 planned ALTER TABLE "orders" ADD COLUMN "gstin" character varying(15) NULL; and CREATE INDEX "orders_gstin_idx". A second run said “Schema is synced, no changes to be made”. No history table appeared in the target.
  2. Atlas, drift. I added a hotfix column remarks by hand. The next dry run planned ALTER TABLE "orders" DROP COLUMN "remarks"; with no warning in the community build. Without --auto-approve and without a terminal the prompt aborted. --exclude 'orders.remarks' kept it out of the plan.
  3. Atlas, concurrent index. With a concurrent_index diff policy in atlas.hcl, the plan became CREATE INDEX CONCURRENTLY, and the apply failed: “CREATE INDEX CONCURRENTLY cannot run inside a transaction block (25001)”. The same apply with --tx-mode none succeeded.
  4. Atlas, versioned. Two migrate diff calls in one script ran in the same second and produced two files with the same version, 20261014125651. Lexical order put add_gstin before create_orders, so migrate apply on an empty database failed with “relation “orders” does not exist” and recorded nothing. Regenerated two seconds apart, the files applied cleanly (2 migrations, 3 statements), with atlas_schema_revisions created inside app. When I appended a comment to an applied file, both migrate validate and migrate apply refused: “checksum mismatch … was edited”.
  5. Atlas, edition check. atlas schema plan replied “not supported by the community version”, and migrate down does not exist in this build. One surprise: atlas migrate lint did run in community v1.3.0 and flagged a test migration that dropped a column (“Dropping non-virtual column”, DS103). Atlas’s Community Edition page lists linting as not included, so I would not build a process on it without checking the build you install.
  6. Flyway, mixed file. With ALTER TABLE and CREATE INDEX CONCURRENTLY in one V2, migrate applied V1 and then refused V2 before running it: “Detected both transactional and non-transactional statements within the same migration (even though mixed is false)”.
  7. Flyway, the hang. I split the change into V2 (the column) and V3 (the concurrent index). With default settings V2 applied and V3 hung. At ten seconds, pg_stat_activity showed Flyway’s lock connection “idle in transaction” holding an advisory lock, and the index build waiting on Lock / virtualxid. I killed the run after 60 seconds. The index then existed and was valid, but flyway_schema_history still said version 2, so the next migrate failed with “relation “orders_gstin_idx” already exists” (SQLSTATE 42P07). A half-applied migration, made by me, in under two minutes.
  8. Flyway, the fix. After dropping the index and setting transactional.lock = false under [flyway.postgresql], V3 applied in about a second. A repeatable R__ view migration ran after the versioned ones. Editing applied V1 made validate fail with both checksums printed. clean refused because cleanDisabled is on by default, and undo replied “Flyway Redgate Edition Required: undo is not supported by OSS Edition”.
  9. Liquibase, apply and roll back. status reported three pending changesets, and quietly created DATABASECHANGELOG and DATABASECHANGELOGLOCK (on a fresh database, update-sql alone created nothing; status created both). update-sql printed the full script including lock statements and checksums. update ran all three. rollback-count --count 1 dropped the index and update restored it. With a GSTIN value stored, rollback-count-sql --count 2 showed the automatic inverse of addColumn: ALTER TABLE app.orders DROP COLUMN gstin;. Changing the column type in an applied changeset failed validation with old and new checksums.
  10. Liquibase, concurrent index and stale lock. A formatted SQL changeset with runInTransaction:false built a concurrent index in about three seconds with default settings. Then I set the lock row to LOCKED = true by hand to imitate a crashed pod. With nothing pending, update simply said the database was up to date. With one pending changeset and a one-minute wait, it printed “Waiting for changelog lock….” and failed with LockException after 62 seconds; list-locks showed my fake holder and release-locks cleared it.

The pg_stat_activity sample from step 7, trimmed:

 state               | wait_event_type | wait_event | query
---------------------+-----------------+------------+-----------------------------------------
 idle in transaction | Client          | ClientRead | SELECT COUNT(*) FROM pg_namespace ...
 active              | Lock            | virtualxid | CREATE INDEX CONCURRENTLY orders_gstin_idx ...

What I did not run: the Atlas standard binary or atlas login, Atlas Cloud, its Kubernetes operator or Terraform provider; any Redgate edition of Flyway (undo, dry run, drift); Liquibase Secure; any engine other than PostgreSQL; concurrent runners racing each other; large tables, lock waits under real traffic, a real CI pipeline; any advisory reproduction. No number above is a benchmark; the seconds only describe waits on a three-row table.

The useful findings are about defaults, not speed. Each tool wraps work in a transaction, and PostgreSQL’s concurrent index build refuses to live there; each needs a different, documented switch. A declarative plan treats every unknown object as drift to remove, which is correct and also dangerous. History tables and checksums work: all three refused edited files. And an interrupted non-transactional step leaves the schema and the history disagreeing, whichever tool you use, so the runbook needs a line for that case.

Unlearn

  • Unlearn “schema migration tool” as one category. A desired-state planner, a versioned runner and a changelog engine ask you to think differently.
  • Unlearn “it is on GitHub, so it is open source”. Check the LICENSE at the tag and the licence of the binary you actually download.
  • Unlearn rollback commands as a safety net for production data. Most of the time the safe path is forward.
  • Unlearn running migrations from every application pod on start-up because it was convenient in the first year.
  • Unlearn trusting the tool’s DDL without reading it. Identity modes, nullability defaults and transaction wrappers differ.

Re-learn

  • Re-learn PostgreSQL lock levels for ALTER TABLE, lock_timeout, and the rules of CREATE INDEX CONCURRENTLY.
  • Re-learn your tool’s history table: what it stores, how its checksums work, and what repair, release-locks or migrate hash really change.
  • Re-learn baselining an existing database, because that is how you adopt or switch any of these tools safely.
  • Re-learn edition boundaries: community and MSA builds, OSS and Redgate editions, Community and Secure.
  • Re-learn version pins with IST timestamps, checksums and the LICENSE at the tag in your runbook.

Closing

Schema migrations in 2026 still look simple until a lock queue builds up behind one ALTER. Atlas brings a desired-state model and a strong integrity file, with an open-core split that decides what you get for free. Flyway remains the plainest way to run ordered SQL files, with its paid features clearly marked. Liquibase offers the richest changelog model and real rollback tooling, now under a Fair Source licence for 5.x. The Coimbatore review becomes useful once it separates the greenfield service from the one with history, and the licence question from the feature question. Learn what each model asks of your team. Unlearn the habit of treating them as interchangeable. Re-learn PostgreSQL locks and your tool’s history table. Practise a concurrent index, an edited file and a crashed run on a throwaway database. Apply the tool that fits your history and your licence policy, and only then ship another ALTER.

Sources

Atlas

Flyway

Liquibase

Security records

PostgreSQL

Background

Internal

comments powered by Disqus

Releted Posts

Change data capture in 2026: revisit Debezium, Apache Flink CDC and PeerDB before you stream another table

Picture a SaaS company in Pune that runs billing for FMCG distributors across Maharashtra (an invented example, not a real client).

Read more

Data pipeline orchestration in 2026: revisit Apache Airflow, Dagster and Prefect before you schedule another DAG

Picture a two-wheeler loan company in Coimbatore with about ninety branches (an invented example, not a real client). Its data team of five runs about 140 cron jobs on two Linux VMs: shell scripts that pull collections files from a payment gateway SFTP, Python scripts that clean them, and SQL that loads a PostgreSQL reporting database every night.

Read more

Authorization in 2026: revisit OpenFGA, SpiceDB and Cerbos before you hard-code another permission check

Picture a SaaS company in Nagpur that sells loan-origination software to cooperative banks across Vidarbha and Marathwada (an invented example, not a real client).

Read more