Skip to content

Atlas CE Drop-In Replacement: What ptah-compat Found in wpmgr

We ran ptah-compat, an Atlas CE drop-in replacement, on wpmgr. It wrote an RLS policy migration Atlas CE skipped, and found schema.sql out of date.

Ptah helps you plan, review, and apply database migrations. Try in your browser

ptah-compat is the part of Ptah that stands in for the Atlas CLI. You point it at an existing Atlas project and run the same commands. It uses the project’s atlas.hcl, migration directory and atlas.sum without changes.

We came across wpmgr by chance and tried it there. wpmgr is an open-source, self-hosted WordPress fleet manager. It uses Atlas Community Edition, and it relies heavily on PostgreSQL row-level security: its migrations create 285 policies. We found a few problems that had nothing to do with Ptah and sent fixes, which wpmgr merged. It also decided to stay on Atlas CE for now.

The most useful result came last, so here it is first. wpmgr still owes a RESTRICTIVE RLS policy on one of its tables. We declared it in the project’s schema file and asked both tools for the next migration. Atlas CE reported the directory in sync and wrote nothing. ptah-compat wrote the policy migration. Afterwards atlas migrate validate still accepted the directory, so both tools can keep working on the same files. The rest of the post shows how we got there, including the schema drift we found on the way.

We used Atlas CE v1.3.0, ptah-compat 0.10.0 and PostgreSQL 16.15, on two wpmgr commits: 2c471d2 from before the fixes and 8b81b3e from after them. Every command and its output is in the supporting files.

ADR-002 in wpmgr’s DECISIONS.md explains the choice. With Atlas CE, one file, db/schema.sql, drives two tools. sqlc reads it to generate typed Go queries, and atlas migrate diff compares it with the migrations to write the next one. The same ADR says the project won’t use Atlas features that need the Pro license or EULA. It calls Atlas’s open-core model a risk and keeps goose as the fallback.

Using ptah-compat as an Atlas CE drop-in replacement

Section titled “Using ptah-compat as an Atlas CE drop-in replacement”

For wpmgr, “drop-in” has a concrete meaning. ptah-compat reads the env "local" block that atlas.hcl already has, uses the same ATLAS_DEV_URL, and writes migrations and atlas.sum in Atlas’s format. Nothing else changes. The server still applies its embedded migrations at startup, self-hosters never run either tool, and the atlas binary still works on the same directory. Ptah is MIT-licensed, which matters to a project that ruled out Atlas’s commercial features on license grounds.

The README tells contributors to create a migration with atlas migrate diff <name> --env local. At 2c471d2 the command failed before it compared anything:

examples/expected/atlas-checksum.txt
You have a checksum error in your migration directory.
L111: 20260803000000_m101_vuln_severity_unknown_feed_alternation.sql was added

atlas.sum hadn’t been updated since m100. We re-hashed the directory and ran the command again. This time Atlas couldn’t load schema.sql, because a policy on sites refers to site_shares, and the file creates site_shares further down.

The bigger problem was what the file said. We applied all 145 migrations to an empty database and got 120 tables and 285 policies in public. schema.sql declared 97 tables and 231 policies. sqlc generates its code from schema.sql, so some generated queries still used columns that m50 had dropped from backup_schedules. One of them fails on a migrated database:

examples/expected/sqlc-query.txt
ERROR: column "notify_on_completion" does not exist

ptah-compat doesn’t read sqlc queries. What it compares is schema.sql with the migrations: migrate diff applies every migration to a throwaway database, loads schema.sql, and writes any difference out as a migration. That difference is what left sqlc generating queries for dropped columns. We found the failing query separately, by preparing the generated SQL on a migrated database. We described what we found in wpmgr#759 and sent a fix in wpmgr#760.

wpmgr took the database changes in wpmgr#762: the corrected schema.sql, the regenerated sqlc code and a re-hashed atlas.sum. The maintainer also moved four statements so the file loads into an empty database in one pass. On the resulting commit, 8b81b3e, we ran both tools:

Terminal window
atlas migrate diff probe --env local
ptah-compat migrate diff probe --env local

Both gave the same answer:

examples/expected/synced.txt
The migration directory is synced with the desired state, no changes to be made

atlas migrate validate --env local passed as well.

wpmgr’s README names the main limitation: Atlas CE “cannot diff RLS policies without login.” In practice Atlas generates the table and index changes, and a developer adds the RLS statements to the migration by hand. No tool checks those statements against schema.sql.

wpmgr already has a case like this waiting. Migration m132 lists seven tables that still need a RESTRICTIVE site_scope policy. We added the first of them, for site_media_settings, to schema.sql, with the same predicate m132 uses for the other tables. Then we asked each tool for a new migration.

Atlas CE printed the same “synced” message and didn’t create a file, even though the policy existed only in schema.sql.

ptah-compat created this migration and updated atlas.sum:

examples/expected/ptah-policy-migration.sql
DROP POLICY IF EXISTS "site_media_settings_site_scope" ON "site_media_settings";
CREATE POLICY "site_media_settings_site_scope" ON "site_media_settings" AS RESTRICTIVE FOR ALL
USING (coalesce(current_setting('app.site_scope', true), '') <> 'on'
OR site_id = ANY (
string_to_array(
nullif(current_setting('app.allowed_site_ids', true), ''), ','
)::uuid[]
))
WITH CHECK (coalesce(current_setting('app.site_scope', true), '') <> 'on'
OR site_id = ANY (
string_to_array(
nullif(current_setting('app.allowed_site_ids', true), ''), ','
)::uuid[]
));

A second run of ptah-compat reported the directory as synced. After that, atlas migrate validate accepted the directory with the new file in it. A team can let ptah-compat write the policy migrations and keep using atlas for everything else.

The new header of schema.sql from #762 says that nothing checks the file automatically yet. A migration that doesn’t update schema.sql still builds and passes CI.

Our #760 proposal included a CI job for this. It applies the migrations with ptah-compat, compares the result with schema.sql, and fails if they differ. If it can’t run the comparison at all, it exits with code 2, so a broken setup never looks like a pass. On the proposal branch the job passed. We then added a migration without touching schema.sql, and the job failed with this output:

examples/expected/guard-drift.txt
DRIFT: db/schema.sql and apps/api/migrations/ describe different schemas.
The statements below turn the migrations' schema into schema.sql's.
Bring schema.sql up to the migrations, or add the migration schema.sql expects:
--- 20260928093327_schema_drift.sql
-- ALTER statements: --
ALTER TABLE "drift_probe" DROP CONSTRAINT IF EXISTS "drift_probe_pkey";
-- WARNING: This will delete all data!
DROP TABLE IF EXISTS "drift_probe" CASCADE;

The SQL shows what schema.sql implies, which is that there is no drift_probe table. The fix is to add the table to schema.sql. Nobody should run this SQL.

For now, wpmgr isn’t adopting ptah-compat, either as a required CI step or as the documented way to write migrations. The maintainer didn’t want to add a new required tool before launch. That’s a fair decision, and it’s theirs to make. Keeping schema.sql in sync automatically is still open in #759.

Here is what we measured. With Atlas CE, the next site_scope policy has to be written by hand, and if someone forgets it, migrate diff still reports that everything is in sync. With ptah-compat, the same command in the same directory writes the policy migration. Trying it means installing one binary. Going back is just as easy, because atlas still validates the directory after ptah-compat has written to it.

These results come from two wpmgr commits, one added policy and PostgreSQL 16. We didn’t run any paid Atlas edition, so we make no claims about it.

You can check whether this matters for your project without switching anything. Work in a separate clone or git worktree, and point ATLAS_DEV_URL at an empty PostgreSQL server with the same major version as production. If your migrations create roles, as wpmgr’s do, also set PTAH_DEV_SERVER_DISPOSABLE=1, and only for a server you can throw away.

Declare one change in your schema file that Atlas CE may skip, such as an RLS policy. Then run, with the env name your atlas.hcl uses:

Terminal window
go install ptah.run/cmd/ptah-compat@v0.10.0
atlas migrate diff trial --env local
ptah-compat migrate diff trial --env local
atlas migrate validate --env local

Compare what each diff writes. If ptah-compat writes a migration before you change anything, your schema file and your migrations already disagree, which is what happened on wpmgr. Delete the copy when you’re done.

For moving a whole project, the adoption guide goes step by step, and the Atlas compatibility overview lists what ptah-compat supports.

Put Ptah to work

Example files