Apply a desired schema
Direct application ships natively as ptah schema apply. The separate
ptah-compat drop-in binary exposes the same engine to scripts that expect an
Atlas-style executable (ptah-compat schema apply); the plan, the approval prompt,
the plan files, and the fingerprint checks behave identically. This page
covers the workflow, the saved plan files that separate review from execution,
and the hybrid patterns that combine a native drift gate with a direct apply.
Prerequisites:
- A
ptahbinary on your machine (Install Ptah). - A desired schema as local files — the examples use a single
schema.sql. - The URL of the database to change.
The examples use a local SQLite database, sqlite://$PWD/app.db, whose one
users table matches schema.sql except for a created_at column added to
the file. Substitute your own database URL throughout.
Native spellings
Section titled “Native spellings”The native verbs use Ptah’s own flag spellings — --db-url for the target
database, --schema-file (SQL, YAML, HCL, DBML, or OCI sources; repeatable),
and --root-dir (Go annotations; repeatable) for the desired schema. These
selectors match schema compare and migrations generate:
ptah schema apply --db-url "sqlite://$PWD/app.db" --schema-file schema.sql --dry-runptah schema plan --db-url "sqlite://$PWD/app.db" --schema-file schema.sql --output change.plan.jsonptah schema apply --db-url "sqlite://$PWD/app.db" --plan change.plan.jsonOn the Atlas-compatible surface, --to additionally accepts a database URL
whose live schema becomes the desired schema, or an Atlas-format migration
directory replayed on the required --dev-url dev database. When --dev-url
is set, the ordered plan is rehearsed on the dev database before the target is
touched, and a failed rehearsal refuses the apply. The rehearsal runs entirely
inside the dev database, which is handed back empty afterwards — see
Atlas schema commands. --lock-timeout
bounds the session advisory lock that serializes concurrent applies,
--tx-mode selects the transaction mode, --edit opens the planned SQL in
$VISUAL/$EDITOR, and --schemas, --include, and --exclude scope both
comparison sides.
Preview the plan
Section titled “Preview the plan”--dry-run prints the planned SQL and stops:
ptah schema apply \ --db-url "sqlite://$PWD/app.db" \ --schema-file schema.sql \ --dry-runExpected output includes:
Planned schema changes:ALTER TABLE "users" ADD COLUMN "created_at" TIMESTAMP;Apply with approval
Section titled “Apply with approval”Without --dry-run, the command shows the same plan and asks for confirmation
before executing; anything other than YES cancels:
ptah schema apply \ --db-url "sqlite://$PWD/app.db" \ --schema-file schema.sqlExpected output includes:
Planned schema changes:ALTER TABLE "users" ADD COLUMN "created_at" TIMESTAMP;Apply these schema changes? Type 'YES' to confirm: Schema apply canceled.--auto-approve skips the prompt for scripted runs:
Planned schema changes:ALTER TABLE "users" ADD COLUMN "created_at" TIMESTAMP;Auto-approval enabled; applying schema changes.Schema apply completed successfully.--edit opens the planned SQL in $VISUAL/$EDITOR before approval, and the
edited SQL is what gets applied.
Separate review from execution with a plan file
Section titled “Separate review from execution with a plan file”Approving whatever the tool plans at execution time is the workflow’s weakest
point. ptah schema plan computes the same plan and saves it as a local JSON
file instead, so the SQL can be reviewed — or code-reviewed — before anything
runs:
ptah schema plan \ --db-url "sqlite://$PWD/app.db" \ --schema-file schema.sql \ --output add-created-at.plan.jsonExpected output includes:
Planned schema changes:ALTER TABLE "users" ADD COLUMN "created_at" TIMESTAMP;Plan saved to file://add-created-at.plan.jsonThe file records the ordered statements with per-statement safety severity and SHA-256 fingerprints of the starting and desired schema states:
{ "format_version": 1, "name": "plan_31a90d35a7bc", "dialect": "sqlite", "from_fingerprint": "sha256:f768e541305e03ee...", "to_fingerprint": "sha256:5fa9d95a6e87c76d...", "destructive": false, "statements": [ { "sql": "ALTER TABLE \"users\" ADD COLUMN \"created_at\" TIMESTAMP", "severity": "safe", "reason": "does not remove data or tighten constraints" } ]}ptah schema apply --plan executes exactly the reviewed statements, after
verifying that the database still matches the plan’s starting fingerprint:
ptah schema apply \ --db-url "sqlite://$PWD/app.db" \ --plan add-created-at.plan.json \ --auto-approveExpected output includes:
Planned schema changes:ALTER TABLE "users" ADD COLUMN "created_at" TIMESTAMP;Auto-approval enabled; applying schema changes.Schema apply completed successfully.A plan file can also carry a reviewer’s signature. ptah schema approve signs
one with an SSH key and ptah schema apply --plan --require-approval refuses a
plan that carries no signature from a list of approvers you commit:
Plan and approve changes.
Verification
Section titled “Verification”After an apply, rerunning the dry run confirms nothing is left to change:
ptah schema apply \ --db-url "sqlite://$PWD/app.db" \ --schema-file schema.sql \ --dry-runExpected output:
Schema is synced, no changes to be made.When the desired schema also exists as SQL, YAML, HCL, or DBML files, Go
annotations, or an OCI artifact, ptah schema drift gives the same
confirmation with
No schema drift detected. and exits 0.
Hybrid patterns
Section titled “Hybrid patterns”- Gate natively, apply on approval.
ptah schema drift --severity destructivein a pipeline blocks data-risking divergence, while routine changes go throughptah schema applywith a saved plan file as the review artifact. Compare and drift covers the gate. - Iterate directly, ship versioned. Prototype against a disposable local
database with
ptah schema apply, then runptah migrations generateagainst a database at the released state, so the reviewed migration file — not the ad-hoc changes — is what reaches shared environments. Generate migrations covers that step.
Failure modes
Section titled “Failure modes”-
Running a plan file against a database that changed since the plan was computed refuses with exit code
2instead of executing reviewed SQL against unreviewed state:error: pre-planned migration is stale: the target database schema does notmatch the plan's source fingerprint (plan sha256:f768e541..., databasesha256:05a1209c...); the database changed since the plan was computed, sore-run `schema plan` against the current database and review the fresh plan -
Declining the confirmation prompt cancels with
Schema apply canceled.and no changes.
Limitations
Section titled “Limitations”--schema-fileaccepts local SQL, YAML, HCL, and DBML files plus OCI schema artifacts.--toaccepts a live database or a migration directory; a migration directory requires a disposable--dev-url. Direct apply does not register--schema-cmdor configuredexternal_schemaexecution.- Registry
atlas://plan URLs are rejected; saved plan files are local. - The Atlas-compatible flag surface,
--envproject-config support, and transaction modes are documented in Atlas schema commands.
Next steps
Section titled “Next steps”- Not sure direct changes fit your project: Choose a workflow.
- Ship the change to shared environments as a reviewed file: Generate migrations.
- See the whole Atlas-compatible surface: Atlas compatibility overview.