What Flyway validate actually checks
On this page
Tool capabilities and edition boundaries described here are accurate as of publication. Both vendors move these lines; check current documentation before making decisions based on this post.
There’s a specific moment this post is written for: your pipeline runs flyway validate, everything is green, and you conclude that your database matches your migrations.
That conclusion doesn’t follow. Not because Flyway is broken, but because validate answers a different question than the one you’re asking.
What validate does
When Flyway applies a migration, it computes a checksum of the file (CRC32 for SQL migrations) and stores it in the flyway_schema_history table alongside the version, description, and execution details.
validate recomputes checksums for the local migration files and compares them against what’s stored in the history table. It also checks that applied migrations still exist locally and flags pending or missing ones.
In other words, validate answers: “Have the migration files changed since they were applied, and is the file set consistent with the recorded history?”
It’s a file-integrity check. A good one — it catches the classic incident where someone edits an already-applied migration (even just fixing a typo in a comment) and every environment that applied the original version now disagrees with the repository.
What it never looks at
Notice what’s absent from that description: the schema itself.
validate reads exactly one thing from your database — the history table. It never inspects your actual tables, columns, indexes, or constraints. So:
- Run a manual
ALTER TABLEin production → files unchanged, history unchanged → validate passes - Another tool adds an index → validate passes
- Someone drops a constraint during an incident and forgets → validate passes
None of this is a bug. The tool is doing precisely what it’s scoped to do. The problem is the widespread belief that it’s scoped to do more.
A useful way to hold it in your head:
| Question | Does validate answer it? |
|---|---|
| Were my migration files edited after being applied? | Yes |
| Are there migrations recorded as applied that no longer exist locally? | Yes |
| Are there pending migrations not yet applied? | Yes |
| Does my database schema match what the migrations describe? | No |
| Did anyone change the schema outside of Flyway? | No |
The last two rows are schema drift, and they are outside the jurisdiction of every command in the default Flyway workflow.
A related footnote: repair doesn’t fix your schema either. It updates the history table — realigning stored checksums with current files, removing failed migration entries. It’s history bookkeeping, same jurisdiction as validate.
What Flyway actually offers for drift
Flyway does address real drift detection — outside the free core workflow.
check -drift compares a target database against the state your migrations should have produced, using snapshots, and generates a report. Recent versions can store a snapshot inside the target database on each successful deploy (-saveSnapshot), so the next run can detect anything that changed out-of-band since. It can also gate deployments: drift found, deployment aborted. This lives in the paid tier — at the time of writing, Flyway’s editions have consolidated such that drift detection is an Enterprise capability.
Community Drift Check exists for PostgreSQL specifically: it compares schema state after your last migration against the state found before the next run, and surfaces out-of-process changes. The nuance: results are viewed through Flyway Pipelines, Redgate’s hosted service — so “free” here means free-with-an-account, wired into their platform.
Both are real capabilities. Neither is the thing most teams have running.
The Liquibase side
Liquibase draws its lines differently, and more generously at the free tier.
diff compares two databases (or a database against a snapshot) and reports differences. diff-changelog goes further and emits changesets that would resolve them. Both are in the open-source distribution.
Two caveats from the practical side:
- Liquibase’s own documentation is direct about
diff-changelogoutput needing human inspection before deployment — some objects and dependencies can’t be represented automatically. That matches everything I’ve written elsewhere about diff generation being only part of the job. - The operational conveniences around drift — like severity flags that turn “unexpected object found” into a configurable CI exit code — are Pro features. OSS gives you the comparison; making it enforce policy in a pipeline is where the paid tier starts.
Also worth knowing if you’re evaluating today: Liquibase has split OSS and Pro into distinct distributions (the 5.0 restructuring), with the OSS core slimmed down and extensions moved to on-demand installation. The capability boundaries above are exactly the kind of thing that shifts during such transitions — hence the note at the top of this post.
The gap that remains
Put the pieces together and the landscape looks like this:
- The command everyone runs (
validate) checks files, not schemas. - The commands that check schemas exist, but sit behind paid tiers, hosted platforms, or changelog-centric workflows with real setup cost.
- The result, in most teams: drift detection is technically available and practically absent. Nobody runs it nightly. Nobody runs it before deployments. It gets run for the first time during the incident retrospective.
If you want the checking habit without the licensing conversation, the previous post in this series builds a pg_dump-based drift check you can put in CI today. And the first post covers why this gap exists at the level of the workflow itself, not any particular tool.
The one-sentence takeaway: green validate means your files are consistent with your history — it is not evidence about your database. Treat it as such, and the mystery production column stops being a mystery.
I build DiffyPick, a desktop tool for comparing live database schemas directly — the “look at the actual database” half of this problem.