How to Evaluate Ptah on an Existing PostgreSQL Database
Try Ptah on a copy of an existing PostgreSQL database: inspect its schema, verify a baseline, generate a migration, and detect drift.
Ptah helps you plan, review, and apply database migrations. Try in your browser
An existing PostgreSQL database has application data and a schema created before Ptah entered the workflow. The team wants to evaluate a database migration tool without giving it control of production. We can run Ptah against a disposable copy, inspect the SQL it proposes, and check the result before deciding on a handoff.
In this trial, Ptah 0.17.0 generated a reviewed migration, replayed it forward, back, and forward on a shadow database, and applied it to the copy. The four invoice rows remained intact. Ptah also detected an extra column introduced outside the desired schema. We used PostgreSQL 18.6. The complete example and recorded output are beside this post.
Start from a populated copy
Section titled “Start from a populated copy”Our sample database has accounts and invoices, a foreign key, an index, and four invoice rows. Its original SQL and seed data stand in for a team’s existing schema and data. We made trial_db from source_db with PostgreSQL’s CREATE DATABASE ... TEMPLATE ...; the setup commands show the copy. All subsequent writes target trial_db or disposable replay databases.
First, let Ptah read what is there and compare it with the original schema file:
ptah schema inspect --db-url "$TRIAL_DB_URL" --schemas public --format hcl > observed.hclptah schema compare --db-url "$TRIAL_DB_URL" --schema-file original-schema.sql --schemas public --exit-codeThe captured HCL contains both tables, the foreign key, and the index. The comparison exited 0 with No schema differences detected. That gives us a checked starting point before writing a migration.
Give the copy a verified baseline
Section titled “Give the copy a verified baseline”We generated an initial migration from the original SQL against empty_db. Ptah replayed the resulting files on shadow_db. Then we previewed the baseline and recorded that migration as already applied on the populated copy:
ptah migrations generate --schema-file original-schema.sql --db-url "$EMPTY_DB_URL" --migrations-dir migrations --name initial --shadow-db "$SHADOW_DB_URL" --schemas publicptah migrations hash --dir migrationsptah migrations baseline --db-url "$TRIAL_DB_URL" --migrations-dir migrations --schemas public --shadow-db "$SHADOW_DB_URL" --dry-runptah migrations baseline --db-url "$TRIAL_DB_URL" --migrations-dir migrations --schemas public --shadow-db "$SHADOW_DB_URL"ptah migrations status --db-url "$TRIAL_DB_URL" --migrations-dir migrations --verify-sum --exit-codeBaselined 1 migration(s) through version 1793797179 in "schema_migrations"The dry run wrote no metadata. The actual baseline replayed the migration on shadow_db and compared its schema with trial_db before recording one revision in the copy. It did not execute the initial CREATE TABLE statements against the populated copy. source_db still had its four invoices and no Ptah revision table. A new run will generate its own version number.
Get the next change from a desired schema
Section titled “Get the next change from a desired schema”The desired SQL adds optional paid_at and an index for status queries. We asked Ptah for the plan before creating files:
ptah migrations plan --schema-file desired-schema.sql --db-url "$TRIAL_DB_URL" --schemas public-- Add/modify columns for table: public.invoicesALTER TABLE "public"."invoices" ADD COLUMN "paid_at" timestamp with time zone;
CREATE INDEX IF NOT EXISTS "invoices_status_idx" ON "public"."invoices" ("status");
Generated 2 migration statements.The full plan classified both schema changes as safe from data loss. We then generated the up and down files. With --shadow-db, Ptah applied the full history, rolled the new migration back, and applied it again before keeping those files:
ptah migrations generate --schema-file desired-schema.sql --db-url "$TRIAL_DB_URL" --migrations-dir migrations --name invoice_tracking --shadow-db "$SHADOW_DB_URL" --schemas publicReviewing that SQL is still the team’s decision. In this run, migrations lint flagged that an ordinary PostgreSQL index build can block writes on a populated table. For a busy production table, plan the index rollout accordingly. The lint output is part of the trial record.
Apply on the copy and check the result
Section titled “Apply on the copy and check the result”After review, we hashed the directory, linted it, previewed the pending execution, and applied the change to trial_db:
ptah migrations hash --dir migrationsptah migrations lint --dir migrations --dialect postgres --latest 1 --fail-on errorptah migrations up --db-url "$TRIAL_DB_URL" --migrations-dir migrations --verify-sum --dry-runptah migrations up --db-url "$TRIAL_DB_URL" --migrations-dir migrations --verify-sumptah schema compare --db-url "$TRIAL_DB_URL" --schema-file desired-schema.sql --schemas public --exit-codeptah migrations status --db-url "$TRIAL_DB_URL" --migrations-dir migrations --verify-sum --exit-codeThe comparison exited 0 with no schema differences. Ptah reported two applied migrations and none pending:
Total Migrations: 2Applied Migrations: 2Pending Migrations: 0Out-of-order Migrations: 0Status: ✅ Database is up to dateThe same data query before and after returned:
invoices | total_cents | open_invoices----------+-------------+--------------- 4 | 24700 | 2(1 row)The recorded row output also shows all four invoice IDs and values. The catalog comparison records the new column and index. The original database remained unchanged.
Finally, we added an unplanned surprise_note column to the copy and ran schema compare --exit-code again. It exited 1 and named public.invoices as modified:
Differences detected (1 category): tables_modified (1): public.invoicesPtah reported the difference; it did not apply its suggested reconciliation. We removed the test column afterward and the comparison passed again.
Decide whether to adopt Ptah
Section titled “Decide whether to adopt Ptah”This trial gives the team concrete artifacts: a captured starting schema, a replayed baseline, reviewed migration files, a dry run, a data check, and an exit code for schema drift. That is enough to decide whether Ptah’s schema-driven workflow fits the next project change. The example directory lets you repeat the same evaluation with a copy of your own database.
Before handing production migration ownership to Ptah, choose who owns the existing history, how the application deploys with a schema change, which role can apply it, and how to recover if deployment stops. Teams arriving from another runner can follow the separate Flyway, Liquibase, or Atlas guides for that handoff. The trial itself requires no production history change.
Put Ptah to work
Example files
- catalog.sql
- check-data.sql
- desired-schema.sql
- measured/baseline-preview.txt
- measured/baseline.excerpt.txt
- measured/baseline.txt
- measured/catalog-final.txt
- measured/catalog-source.txt
- measured/commands.json
- measured/compare-drift.txt
- measured/compare-final.txt
- measured/compare-original.txt
- measured/compare-restored.txt
- measured/data-after.txt
- measured/data-before.txt
- measured/data-summary.excerpt.txt
- measured/drift.excerpt.txt
- measured/final-schema.hcl
- measured/generate-initial.txt
- measured/generate-next.txt
- measured/hash-final.txt
- measured/hash-initial.txt
- measured/initial-schema.hcl
- measured/introduce-drift.txt
- measured/lint-with-dev.txt
- measured/lint.txt
- measured/plan-next.txt
- measured/plan-safety.excerpt.txt
- measured/plan-sql.excerpt.txt
- measured/postgres-version.txt
- measured/ptah-version.txt
- measured/remove-drift.txt
- measured/shadow-replay.excerpt.txt
- measured/source-unchanged.txt
- measured/status-after-baseline.txt
- measured/status-final.txt
- measured/status.excerpt.txt
- measured/up-preview.txt
- measured/up.txt
- migrations/1793797179_initial.down.sql
- migrations/1793797179_initial.up.sql
- migrations/1793797229_invoice_tracking.down.sql
- migrations/1793797229_invoice_tracking.up.sql
- migrations/ptah.sum
- original-schema.sql
- README.md
- seed.sql
- verified.json