From Atlas to Ptah: Keep the History, Control the Deployment
Continue an Atlas migration history with Ptah, catch a changed payment-queue view, and test both CLIs without external network access.
Ptah helps you plan, review, and apply database migrations. Try in your browser
A payment queue already has an Atlas migration history. Its next release adds a reference column and a view of unpaid invoices. The deployment team wants to keep the existing SQL and revision records while operating the migration runner and schema checks inside its own network.
We tested that handover with Ptah 0.12.0, PostgreSQL 18.6, and Atlas Community Edition compiled from v1.3.0 source. The supporting files contain the build command, complete fixtures, and recorded results. All invoices are synthetic.
This comparison follows a source-only rule for Atlas. We compiled CE ourselves; we did not download or run its commercial distribution. Statements about Atlas Pro below are theoretical comparisons based on official documentation read on September 21, 2026, not results from a Pro trial.
Keep the directory and the revision table
Section titled “Keep the directory and the revision table”The starting directory has two Atlas SQL files and an atlas.sum. CE applies
both files, creating app.invoices and then its paid column. We insert an
unpaid invoice for Heron and a paid invoice for Badger.
The next migration adds the payment queue:
ALTER TABLE app.invoices ADD COLUMN reference text;CREATE VIEW app.unpaid_invoices AS SELECT id, customer, total_cents FROM app.invoices WHERE NOT paid;Ptah can read an Atlas directory and its revision-table format directly. These
are separate choices: the file convention does not determine which database
table Ptah reads. Our existing history lives in
atlas_schema_revisions.atlas_schema_revisions.
With PTAH_DB_URL set to the lab database, we prepare the directory and preview
the handover:
ptah migrations hash --dir migrations --dir-format atlasptah migrations up --migrations-dir migrations \ --dir-format atlas --revision-format atlas \ --migrations-schema atlas_schema_revisions --verify-sum --dry-runHashing records the reviewed files in atlas.sum. It does not authenticate
their author or establish that the SQL is correct. The dry run reports:
Current version: 20260921000200Total migrations: 3Pending migrations: 1Both existing versions remain applied. The dry run leaves the recorded revision values unchanged. Omitting the revision-format setting would select Ptah’s native history layout instead; that is not the history this database holds. Check the actual table location before changing a deployment command.
Apply the pending file with the same settings:
ptah migrations up --migrations-dir migrations \ --dir-format atlas --revision-format atlas \ --migrations-schema atlas_schema_revisions --verify-sumptah schema compare --schema-file schema.sql --schemas app --exit-codeThe database advances to 20260921000300. The final comparison finds no schema
differences. The two original invoices retain their values; the new nullable
reference is empty on those rows. A SQL client inserts invoice 3 for Otter and
reads the view. The queue contains invoices 1 and 3, with totals 12500
and 4200 cents.
A second Ptah run finds zero pending migrations. Atlas CE subsequently reads the same revision table and reports three executed files with none pending. We also replayed the complete directory on separate empty databases with each CLI. Both reached the expected schema and produced the same queue after the same inserts. This establishes compatibility for these files and completed revisions; it does not cover every Atlas directive or an interrupted migration.
Check the view that the application reads
Section titled “Check the view that the application reads”Executing SQL and understanding its schema objects are different capabilities.
Both CLIs executed the file containing CREATE VIEW. In the resulting database,
CE’s SQL inspection included the invoice table but omitted the view. Ptah’s
inspection included both.
We then changed the view’s filter from WHERE NOT paid to WHERE paid. The
query now returned Badger’s paid invoice, 2, instead of invoices 1 and 3.
The table and migration history still looked correct.
Comparing that database against schema.sql produced different results:
| Check on the altered view | Observed result |
|---|---|
Source-built Atlas CE schema diff |
Reported synchronized schemas; exit 0 |
Ptah schema compare --exit-code |
Reported views_modified for app.unpaid_invoices; exit 1 |
| SQL query through the view | Returned invoice 2, confirming the application-facing error |
Ptah printed SQL to restore the view definition. The comparison did not execute it. We restored the definition explicitly, then checked that the queue again returned invoices 1 and 3 and the schema comparison passed.
This is a reason to evaluate the objects an application actually uses. It is not a result about Atlas Pro: the standard distribution’s documented object coverage includes views and other objects omitted from CE. A source-built CE comparison cannot establish parity with that distribution.
Give both tools credit for the checks they passed
Section titled “Give both tools credit for the checks they passed”We changed a migration after hashing it. Both tools refused the altered directory: CE returned 1, and Ptah returned 2. Neither needed a vendor service to detect that mismatch.
A separate migration drops the customer column. Both linters found the risk:
| Tested linter | Finding | Exit |
|---|---|---|
Atlas CE migrate lint --latest 1 |
DS103, destructive column removal |
1 |
Ptah migrations lint --latest 1 |
DS102, data loss; BC104, deployed-code compatibility |
1 |
Ptah also warned that DS110P lacked its baseline schema. That run was thinner
than a complete database-backed analysis, even though it detected the drop.
The linter article examines the boundary
between static findings and tests of database or application behavior.
The current Atlas feature page lists migration linting outside CE, but the
pinned source build above demonstrably ran its linter. Use the recorded build
and command result when evaluating this case. CE’s migrate down command did
explicitly refuse execution as unsupported. We did not execute a rollback in
this comparison.
Separate offline execution from telemetry and licensing
Section titled “Separate offline execution from telemetry and licensing”The runtime lab used a container with Docker’s none network. PostgreSQL and
both binaries ran inside it, communicating over loopback. It had no external
network interface or default route. Source and dependency downloads happened
before the isolated run.
CE received ATLAS_NO_UPDATE_NOTIFIER=1, ATLAS_NO_ANON_TELEMETRY=1, and
ATLAS_NO_UPGRADE_SUGGESTIONS=1. Its
pinned entry point
checks the update-notifier variable before contacting the version-check service.
Neither CLI received an Atlas Cloud account token. Migration execution,
inspection, and the checks above completed inside this boundary.
That proves the tested local workflows do not require an internet response. It does not measure every attempted connection or cover features configured to use external registries, secret stores, or model providers. CE itself is therefore a viable offline option for the capabilities this run demonstrated.
The commercial distribution needs a separate evaluation. Atlas’s privacy documentation distinguishes anonymous telemetry from identified-user telemetry. The documented anonymous opt-out disables anonymous collection. For logged-in Pro users, the page lists command and runtime information, local hostname, database type, CLI version, and licensing identifiers. It says database contents and passwords are not collected, and describes commands or flags that upload schemas and migrations. An anonymous opt-out is not a documented switch for every category of cloud communication.
The Atlas Trust Center treats license verification separately. It documents offline user licenses, custom Enterprise offline licenses for CI/CD, and cached grants for standard pipelines. Those options matter when assessing disconnected operation. We did not test their behavior, expiry, or commercial terms.
Ptah’s tested release has no account or license-grant step in this workflow.
Its default build has a
no-op OpenTelemetry implementation.
A build with the observability tag can enable
OTLP tracing through an explicit endpoint.
User-configured observability is still data leaving a process; its destination
belongs in the deployment’s network policy.
Own the deployment path
Section titled “Own the deployment path”For a deployment with sovereignty requirements, the useful questions are concrete: where do artifacts live, who can approve a change, what credentials can the runner use, and which outbound services are required?
Ptah’s MIT-licensed source lets this deployment retain its runner implementation and build process. Atlas CE’s source license is Apache 2.0. The distinction for this payment queue is the tested schema coverage and the deployment features the team needs around it, not whether CE can run locally.
The CI example exercises scoped database roles and checks before starting an application. The Harbor example stores schema artifacts in a private registry and passes a pinned digest to the Ptah operator. Those are separately verified ways to operate the surrounding workflow under your own control.
A locally running CLI does not establish that an entire cloud control plane can be self-hosted. This example makes no such claim about Atlas Cloud, and it does not qualify either product for a regulatory regime. It shows a narrower operational result: the payment queue moved to Ptah without replaying its existing history, and its view became part of the schema check.
Put Ptah to work
Example files
- application.sql
- atlas-history/20260921000100_create_invoices.sql
- atlas-history/20260921000200_add_paid.sql
- atlas-history/atlas.sum
- build-provenance.json
- history.sql
- measured/after-data.txt
- measured/after-dry-history.txt
- measured/after-history.txt
- measured/application.txt
- measured/atlas_replay-application.txt
- measured/atlas_replay-compare.txt
- measured/atlas_replay-seed.txt
- measured/atlas-apply.txt
- measured/atlas-checksum-refused.stderr.txt
- measured/atlas-checksum-refused.txt
- measured/atlas-down-unavailable.stderr.txt
- measured/atlas-final-status.txt
- measured/atlas-inspect.txt
- measured/atlas-lint.stderr.txt
- measured/atlas-lint.txt
- measured/atlas-replay.txt
- measured/atlas-version.txt
- measured/atlas-view-diff.txt
- measured/audit-final-schema.txt
- measured/audit-revisions.txt
- measured/audit-rows.txt
- measured/before-data.txt
- measured/before-history.txt
- measured/commands.json
- measured/correct-queue.txt
- measured/drift.txt
- measured/final-compare.txt
- measured/final-data.txt
- measured/final-history.txt
- measured/interfaces.txt
- measured/module-verification.txt
- measured/network-final.txt
- measured/network.txt
- measured/postgres-version.txt
- measured/ptah_replay-application.txt
- measured/ptah_replay-compare.txt
- measured/ptah_replay-seed.txt
- measured/ptah-apply.stderr.txt
- measured/ptah-apply.txt
- measured/ptah-checksum-refused.stderr.txt
- measured/ptah-compare.txt
- measured/ptah-drift-refused.txt
- measured/ptah-dry-run.stderr.txt
- measured/ptah-dry-run.txt
- measured/ptah-hash.txt
- measured/ptah-inspect.stderr.txt
- measured/ptah-inspect.txt
- measured/ptah-lint.stderr.txt
- measured/ptah-lint.txt
- measured/ptah-replay.stderr.txt
- measured/ptah-replay.txt
- measured/ptah-retry.txt
- measured/ptah-version.txt
- measured/ptah-view-drift.txt
- measured/restore.txt
- measured/restored-view-compare.txt
- measured/routes.txt
- measured/seed.txt
- measured/view-drift.txt
- measured/view-restore.txt
- measured/wrong-queue.txt
- migrations/20260921000100_create_invoices.sql
- migrations/20260921000200_add_paid.sql
- migrations/20260921000300_payment_queue.sql
- migrations/atlas.sum
- README.md
- risky/20260921000100_create_invoices.sql
- risky/20260921000200_add_paid.sql
- risky/20260921000300_drop_customer.sql
- risky/atlas.sum
- schema.sql
- seed.sql
- state.sql
- tampered/20260921000100_create_invoices.sql
- tampered/20260921000200_add_paid.sql
- tampered/20260921000300_payment_queue.sql
- tampered/atlas.sum
- verified.json