Skip to content

From Flyway to Ptah Without Replaying Production

Import a Flyway history, verify it against an existing PostgreSQL database, and let Ptah apply the next change while preserving the application's data.

An existing catalog database already has its tables, indexes, and product rows. Moving its migrations from Flyway to Ptah should preserve that state. Running the imported history against it would try to create objects that already exist.

Ptah’s baseline command can record the imported migrations as applied. Before doing that, we’ll have it replay the files in a disposable database and compare the result with the existing schema. Then we’ll use Ptah to generate and apply the next catalog change.

This run used Flyway OSS 13.7.0, Ptah 0.8.0, and PostgreSQL 18.6. The example files include the original migrations, imported files, image identity, and captured results.

Start with the history the database actually ran

Section titled “Start with the history the database actually ran”

Flyway applied these files to our test database:

File Result
V1__products.sql Product table with SKU, stock, and archive status
V2__available_stock.sql Partial index for available stock
R__available_products.sql View selecting products with positive stock that are not archived

We then inserted four application rows. KIT-42 and CABLE-7 appeared in the availability view; another product had no stock, and one was archived.

Flyway’s history table lives in the flyway schema in this example. Application objects live in public. That separation lets us compare the application schema while keeping the original history for reference. The full setup and Flyway commands are in the supporting files.

Freeze changes through Flyway for the handoff. Import the exact files that produced the database state you intend to adopt:

Terminal window
ptah migrations import --from flyway \
--source-dir source --migrations-dir migrations --dry-run
ptah migrations import --from flyway \
--source-dir source --migrations-dir migrations

The import created up/down pairs and ptah.sum. Its up files were:

examples/expected/imported.txt
0000000001_products.up.sql
0000000002_available_stock.up.sql
0000000003_repeatable_available_products.up.sql

DB_URL names the existing application database. SHADOW_URL must name a different, disposable database: Ptah resets it while checking the history.

Preview the metadata rows, then perform the verified baseline:

Terminal window
ptah migrations baseline --db-url "$DB_URL" \
--migrations-dir migrations --version 3 --schemas public \
--shadow-db "$SHADOW_URL" --dry-run
ptah migrations baseline --db-url "$DB_URL" \
--migrations-dir migrations --version 3 --schemas public \
--shadow-db "$SHADOW_URL"

The dry run shows the proposed rows. The second command replays versions 1–3 in the shadow database and checks their schema against the target before recording them. Its result was:

examples/expected/baseline.txt
Baselined 3 migration(s) through version 3 in "schema_migrations"

The migration messages during this check describe work in the shadow database. The application database kept all four product rows and its original Flyway history. Ptah created its own revision records; it did not convert Flyway’s installation timestamps or checksums into Ptah history.

Check that nothing is pending:

Terminal window
ptah migrations status --db-url "$DB_URL" \
--migrations-dir migrations --verify-sum

The current version was 3, with three applied migrations and zero pending.

Check that a mismatched history is refused

Section titled “Check that a mismatched history is refused”

We also ran a separate Flyway database to test the failure case. Its repeatable view initially selected stock greater than zero. Changing the same R__ file to require at least five units caused Flyway to run that file again. The view then returned only KIT-42.

Trying to baseline that database against the earlier imported snapshot failed with baseline shadow check failed: view mismatch available_products. Ptah returned exit code 2, and its revision table was still absent afterward.

This is why a baseline needs a schema check. The source can be readable and the import successful while the imported history describes a different state.

The catalog now needs a configurable reorder threshold. The new desired schema adds reorder_level, defaulting to five, and makes the availability view use that value.

We first normalized the desired SQL through PostgreSQL:

Terminal window
ptah schema inspect --schema-file schema-v2.sql \
--dev-url "$SHADOW_URL" --schemas public --format hcl > schema-v2.hcl
ptah migrations generate --db-url "$DB_URL" \
--schema-file schema-v2.hcl --schemas public \
--migrations-dir migrations --name reorder_threshold \
--shadow-db "$SHADOW_URL"

This normalization mattered in the tested release: passing the original SQL directly made the shadow check report a view-body mismatch after PostgreSQL reformatted it. schema inspect gives Ptah the database’s stored form. Both the failed direct-SQL check and the successful normalized run are recorded.

The generated up migration contains:

examples/expected/new-change.sql
ALTER TABLE "public"."products" ADD COLUMN "reorder_level" integer NOT NULL DEFAULT 5;
CREATE OR REPLACE VIEW "public"."available_products" AS
SELECT id,
sku,
stock
FROM products
WHERE stock >= reorder_level AND NOT archived;

The shadow check replayed the imported history and new migration, rolled back the new migration, and reapplied it. That validates this new migration’s round trip; it does not supply the missing rollback SQL for the older files.

Apply the reviewed files and compare the result

Section titled “Apply the reviewed files and compare the result”

After reviewing the generated pair, update the directory checksum and lint the new version:

Terminal window
ptah migrations hash --dir migrations
ptah migrations lint --dir migrations --dialect postgres --latest 1
ptah migrations up --db-url "$DB_URL" \
--migrations-dir migrations --verify-sum
ptah schema compare --db-url "$DB_URL" \
--schema-file schema-v2.hcl --schemas public --exit-code

The lint run reported no findings. Ptah applied one pending migration and reported no schema differences. All four product rows remained, each with reorder_level = 5; only KIT-42 appeared in the updated availability view.

Finally, we replayed the complete directory on an empty database:

Terminal window
ptah migrations up --db-url "$FRESH_URL" \
--migrations-dir migrations --verify-sum
ptah schema compare --db-url "$FRESH_URL" \
--schema-file schema-v2.hcl --schemas public --exit-code

That database applied all four migrations and reached the same schema. The existing database applied only the new one. Both had no pending migrations.

Keep the verified imported history and its checksum in Git, retire Flyway execution for this database, and make subsequent changes through new Ptah migrations. The handoff is complete when existing and fresh databases agree on the schema, with the existing application’s data preserved.

Example files