Database Migration Tools: Ptah, Atlas, Flyway, Liquibase, and Goose
Choose a database migration tool by its source of truth, review workflow, drift check, database coverage, and deployment model.
Ptah helps you plan, review, and apply database migrations. Try in your browser
A Go service needs a new PostgreSQL column. Its team wants to review the SQL before deployment, know which changes ran, and catch a column added outside the release process. Those are three separate jobs. A migration history can answer the second while the live database has already changed.
Ptah, Atlas, Flyway, Liquibase, and Goose all run database changes, but they start from different inputs and provide different checks. We use two small PostgreSQL journeys below to make the distinction concrete. The runnable files and full output are beside this guide.
-
Desired schema
Ptah · Atlas
Describe the target and generate a change. Both also run versioned migrations.
-
Ordered migration files
Flyway OSS · Goose
Review SQL and apply files in order. Goose can also run Go functions.
-
Conditional changelog
Liquibase Community
Select changesets by context or label and check preconditions.
-
Migration history
Which versions ran? Are files intact?
-
Live schema drift
Does the database match the source?
A clean history check does not answer the live-schema question.
Versions and evidence
Section titled “Versions and evidence”Updated November 10, 2026. Each version below is the one used for the linked observation. Edition boundaries are part of the comparison. The runs are small fixtures, not deployment performance tests.
| Tool | Version | Evidence used here |
|---|---|---|
| Ptah | 0.25.0 | PostgreSQL run in this guide and the 0.25.0 Atlas comparison |
| Atlas Community Edition, source-built | 1.3.0 | SQLite plan and apply run; edition matrix for Pro features |
| Flyway OSS | 13.12.0 | PostgreSQL migration and drift run |
| Liquibase Community | 5.0.4 | PostgreSQL changelog and drift run |
| Goose | 3.29.0 | PostgreSQL SQL migration run in this guide |
Start with the input your team wants to maintain
Section titled “Start with the input your team wants to maintain”| Tool | Primary input in the workflow discussed here | What it supplies |
|---|---|---|
| Ptah | A desired schema file or handwritten versioned SQL | Planning, generated or handwritten migrations, and live comparison |
| Atlas | A desired schema or a versioned migration directory | Declarative and versioned workflows in CE; some review and drift operations depend on edition |
| Flyway OSS | Ordered SQL migration files | migrate and history validate |
| Liquibase Community | A changelog of changesets, including formatted SQL | update, update-sql, and changeset selection |
| Goose | Ordered SQL files or Go migration functions | up, down, and status through a CLI or Go package |
The source of truth drives the choice. Ptah and Atlas can keep a desired schema and derive a change. Flyway OSS and Goose make the migration files the central artifact. Liquibase keeps a changelog with identity, selection, and conditional execution rules that can be more important than the SQL alone. Flyway’s commercial editions add schema comparison and generation; the command tiers identify which edition supplies each operation.
What changes in day-to-day work
Section titled “What changes in day-to-day work”Ptah: keep a target schema or write migrations
Section titled “Ptah: keep a target schema or write migrations”Ptah has a direct schema path
and a versioned migration path.
The direct path compares a desired file with a database and can save a plan
whose exact bytes are signed before apply. The versioned path can generate
up and down SQL against a disposable shadow database, or run SQL the team
wrote. It records revisions and can compare the live schema with a release’s
desired file. The signed direct-plan approval does not gate an ordinary
migrations up; use the appropriate deployment policy for that path.
Atlas: check which edition supplies the workflow
Section titled “Atlas: check which edition supplies the workflow”Atlas CE can inspect, diff, and apply a desired schema, and it can run
versioned migrations. In our source-built CE 1.3.0 run,
schema apply --dry-run previewed a SQLite column, while schema plan was
unavailable. The Atlas feature matrix assigns
saved planning, drift detection, and managed rollout features to paid tiers.
Its Kubernetes Operator
supports desired-schema and versioned resources. A source-built CE binary,
the standard download, and a Pro-connected deployment have different terms
and commands; pin the one your team will use.
Flyway: let the history directory lead
Section titled “Flyway: let the history directory lead”Flyway OSS applies ordered migration files and stores their checksums in a
schema history table. Its validate command checks that history against the
files. In our PostgreSQL run, it rejected an
edited applied migration, while a column added outside Flyway left
validate green. The command tiers
place a deployment dry run and undo in Teams, and schema drift detection
in Enterprise. A team with a working Flyway pipeline can keep its runner and
add a separate read-only schema check.
Liquibase: keep conditions with the change
Section titled “Liquibase: keep conditions with the change”A Liquibase changeset has an id, an author, and a changelog path as its
identity. Community can preview SQL with update-sql
before update. The changelog can also carry
contexts,
labels, preconditions, and runAlways. Contexts and labels filter changesets
when the deployment supplies a matching filter. Preconditions check the
database state, while runAlways reruns a changeset on each update. That
policy travels with the changelog;
a plain ordered SQL directory does not preserve it. Our
Community 5.0.4 run
also showed that diff reported a manual column by comparing two databases,
but returned 0. A pipeline that needs a failing drift gate must handle that
report or choose a different check.
Goose: keep migrations near the Go program
Section titled “Goose: keep migrations near the Go program”Goose accepts SQL files and registered Go migration functions. Its
Go API
can apply SQL embedded in an application binary; create and fix still
work on files. The measured CLI path below used a migration directory,
database URL, and up/status. After a manual schema change, status
continued to report the two applied versions. We added Ptah as a read-only
check of the desired schema; Goose still owned execution and history.
A Go developer: keep Goose, add a live check
Section titled “A Go developer: keep Goose, add a live check”Goose fits a service that already owns SQL migrations and wants a small Go runner. Its Go package can also apply embedded SQL files. We used its CLI and SQL files here; an embedded runner is a separate integration choice.
The first migration creates public.products and four sample rows. The next
adds a reorder threshold:
-- +goose UpALTER TABLE public.products ADD COLUMN reorder_level integer NOT NULL DEFAULT 5;
-- +goose DownALTER TABLE public.products DROP COLUMN reorder_level;With the disposable database and connection variable from the setup, we applied version 1, inserted the rows, and then ran the pending migration:
goose -dir goose/migrations postgres "$GOOSE_DB_URL" up-to 1goose -dir goose/migrations postgres "$GOOSE_DB_URL" upgoose -dir goose/migrations postgres "$GOOSE_DB_URL" statusGoose recorded both versions. The resulting rows were:
id,sku,stock,archived,reorder_level1,KIT-42,12,f,52,CABLE-7,3,f,53,ADAPTER-2,0,f,54,OLD-KIT,8,t,5Now the question changes. Does the live public schema still match the
release? We used the desired schema file as a read-only
Ptah check beside Goose. The Goose history table is excluded because it is
not an application table:
ptah schema drift --db-url "$GOOSE_DB_URL" --schema-file schema.sql --schemas public --exclude public.goose_db_versionIt first returned 0 with No schema drift detected. We then added
manual_note outside the migrations. Goose status still showed both
versions as applied. The same Ptah command returned 1:
Findings:- columns_removed: 1 (destructive)The label describes what reconciliation to the file would remove; the check did not remove the column. Goose remains the migration runner in this arrangement. Ptah adds a separate schema check, which needs a schema file matching the release currently deployed. This example does not import or rewrite Goose history. Our Flyway comparison shows the same history-versus-live-schema distinction with Flyway OSS.
Check on goose_lab |
Before manual column | After manual column |
|---|---|---|
Goose status |
Both versions applied; exit 0 | Both versions applied; exit 0 |
Ptah schema drift |
No drift; exit 0 | columns_removed; exit 1 |
public.products.manual_note
History is current exit 0
columns_removed: 1
Live schema changed exit 1
Review the column before reconciling. The checks report different facts about the same database.
The second result is a report about the difference, not an instruction to drop the column. The release owner must decide whether to preserve the manual change or amend the desired file. A CI job should fail and ask for that decision before executing any reconciliation SQL.
A team deployment: review generated SQL before apply
Section titled “A team deployment: review generated SQL before apply”Another team wants a schema file in Git to describe the target, generated
SQL to review in a pull request, and a check after deployment. We used the
same starting table and rows in a separate database. In schema.sql, the
new column appears as part of the desired table:
CREATE TABLE public.products ( id bigint PRIMARY KEY, sku text NOT NULL UNIQUE, stock integer NOT NULL DEFAULT 0, archived boolean NOT NULL DEFAULT false, reorder_level integer NOT NULL DEFAULT 5);The setup applies version 1 and seeds the table. Generation uses a separate disposable shadow database. Ptah resets that database and replays the migration up, down, and up while checking the files:
ptah migrations generate --db-url "$PTAH_DB_URL" --schema-file schema.sql --schemas public --migrations-dir ptah/migrations --name reorder_level --shadow-db "$PTAH_SHADOW_URL"ptah migrations hash --dir ptah/migrationsptah migrations up --db-url "$PTAH_DB_URL" --migrations-dir ptah/migrations --verify-sum --dry-runptah migrations up --db-url "$PTAH_DB_URL" --migrations-dir ptah/migrations --verify-sumptah schema compare --db-url "$PTAH_DB_URL" --schema-file schema.sql --schemas public --exit-codeReviewers can read both generated files and the dry run before the apply step. The up file contained:
ALTER TABLE "public"."products" ADD COLUMN "reorder_level" integer NOT NULL DEFAULT 5;The down file drops the column. It would also lose values written there, so a successful shadow replay is not a recovery plan for production data. In our run, the apply recorded the new version, the final comparison reported no schema differences, and the four application rows matched the Goose path. The full dry run, generated files, and output show the exact sequence.
| Stage | What the team sees | What the command enforces |
|---|---|---|
| Generate | Desired schema and generated up/down files | Shadow replay checks that this pair runs on the disposable fixture. |
| Review | The SQL diff in a pull request | The pull request process records human review; Ptah has not applied the SQL. |
| Preview | One pending version and its SQL in the dry run | --dry-run makes no database change. |
| Apply | The reviewed migration directory | --verify-sum checks the directory integrity; Ptah records the applied version. |
| Compare | No schema differences | --exit-code fails if the live database differs from the desired file. |
A pull request review is a team process around these versioned files. It is
not a Ptah signature gate on migrations up. If approval must be enforced by
the tool, Ptah has a signed saved-plan flow for
direct schema changes. Its Kubernetes Operator workflow
has a separate plan approval policy. Atlas also has a
Kubernetes Operator for
declarative and versioned resources. Compare the actual deployment path and
edition before treating those controls as interchangeable.
Checks, database coverage, and cost
Section titled “Checks, database coverage, and cost”Migration history and live schema are different evidence. Our
Flyway OSS run found that validate rejected
an edited applied file while an out-of-band column left validate green.
Flyway’s check -drift
is in Enterprise. In our Liquibase Community run,
status and validate likewise stayed green after a manual change.
Community’s diff compared two databases and reported the column, but exited
0. Ptah’s file-to-database drift command returned 1 in the Goose journey
above. Atlas’s edition matrix separates CE
inspection from its Pro drift workflow. Those commands do not all check the
same inputs or provide the same CI signal.
Schema inspection answers another question: what objects exist now? Ptah’s
schema inspect and Atlas’s
schema inspect export live definitions.
Flyway info and Goose status in our runs reported migration history.
Liquibase Community diff compared two live databases. Match the command to
the evidence you need.
Database names alone are not a support guarantee. Check the object types and
operations you need in the Ptah support matrix,
Atlas edition and database matrix,
Flyway database matrix,
Liquibase supported databases,
and Goose dialect list.
PostgreSQL row-level security shows why the object list matters. Ptah’s
PostgreSQL model includes
policies. Atlas lists RLS among its
Pro-only PostgreSQL objects. Our
Liquibase Community fixture could apply
SQL affecting RLS, but its diff did not report the tested policy change.
A runner that executes SQL and a tool that models the resulting object offer
different coverage.
The extra infrastructure depends on the chosen job. Ptah generation needs a disposable shadow database, while its handwritten migration path does not. Atlas migration generation uses a dev database. Liquibase Community 5.0.4 needed a PostgreSQL driver installed in our measured setup. Goose’s SQL path used a directory, a connection, and its binary. A Ptah or Atlas Operator adds a controller and Kubernetes resources when that reconciliation model is useful. None is required for the CLI journeys here.
Licenses also differ. Ptah and Goose use MIT. Atlas CE and the Flyway open-source project use Apache 2.0; their downloadable or paid features have separate terms. Liquibase Community 5.0.4 uses FSL-1.1-ALv2. Check the edition and license of the binary you deploy, not only the repository name.
Which tool fits the next change?
Section titled “Which tool fits the next change?”| Need | Start here | Decision boundary |
|---|---|---|
| Desired schema, reviewed SQL, and an open-source live drift gate | Ptah | Check the engine’s object coverage and the shadow database needed for generation. |
| Desired schema with Atlas Cloud, its registry, or managed review workflows | Atlas | CE and Pro differ; pin the edition. |
| A working versioned SQL pipeline with Flyway history | Flyway | Keep it when switching runners adds risk; compare its paid checks if drift is the missing job. |
| Conditional or repeatable changesets across environments | Liquibase | Contexts, labels, preconditions, and runAlways are distinct from a plain migration directory. |
| SQL or Go migrations close to a Go application | Goose | Add a separate live-schema check if the application needs one. |
For a Go application that previously changed its schema on startup, our GORM migration case follows the move to reviewed SQL. The wpmgr case shows why object coverage, including an RLS policy, can decide an Atlas migration. For Kubernetes delivery, the OCI-to-plan walkthrough shows the separate publication, approval, and apply steps.
Ptah 0.25.0 is pre-GA. Pin its release and check CLI changes during upgrades.
Can I use a drift checker without changing migration runners?
Section titled “Can I use a drift checker without changing migration runners?”Yes. The Goose journey did exactly that. Keep the runner’s history table out of the desired application schema, or exclude it from the comparison. Use the schema file for the release already deployed before an upgrade, then the new file after applying it. Otherwise the planned change looks like drift.
Does a dry run replace approval?
Section titled “Does a dry run replace approval?”No. A dry run previews SQL. It does not identify who approved those bytes. Use your pull request process for versioned files, or choose a workflow with an enforced approval object or signature when that is a requirement.
Should I move an existing changelog to Ptah?
Section titled “Should I move an existing changelog to Ptah?”Only after checking what the changelog means. Our Liquibase comparison
shows that Ptah refuses import of changesets with contexts, labels,
preconditions, or runAlways; silently dropping those conditions would change
deployment behavior. The Flyway handoff and
Atlas handoff follow different histories. A team can
also keep its existing runner and add a read-only Ptah check first.
Put Ptah to work
Example files
- goose/migrations/00001_products.sql
- goose/migrations/00002_reorder_level.sql
- manual-drift.sql
- measured/goose-ptah-drift-clean.txt
- measured/goose-ptah-drift-detected.txt
- measured/goose-ptah-drift.excerpt.txt
- measured/goose-rows.txt
- measured/goose-seed.txt
- measured/goose-status-after-drift.txt
- measured/goose-status-after.txt
- measured/goose-status-before.txt
- measured/goose-up-v1.txt
- measured/goose-up-v2.txt
- measured/goose-version.txt
- measured/manual-drift-apply.txt
- measured/postgres-version.txt
- measured/ptah-compare.txt
- measured/ptah-dry-run.txt
- measured/ptah-generate.txt
- measured/ptah-hash-v1.txt
- measured/ptah-hash-v2.txt
- measured/ptah-rows.txt
- measured/ptah-seed.txt
- measured/ptah-status.txt
- measured/ptah-up-v1.txt
- measured/ptah-up-v2.txt
- measured/ptah-up.excerpt.sql
- measured/ptah-version.txt
- measured/run-record.json
- ptah/migrations/0000000001_products.down.sql
- ptah/migrations/0000000001_products.up.sql
- ptah/migrations/1794308564_reorder_level.down.sql
- ptah/migrations/1794308564_reorder_level.up.sql
- ptah/migrations/ptah.sum
- README.md
- schema.sql
- seed.sql
- verified.json