Choose a workflow
For schema changes, wiring Ptah into a project answers two questions, and they are independent.
Persistent inference state follows a separate generation lifecycle. Start with Inference migrations.
Where does the change come from? Either you write the migration yourself —
ptah migrations create, then the SQL — or you describe the schema you want and
Ptah works out the difference. The first needs no schema source at all; the
second reads one from SQL, YAML, HCL, or DBML files, Go annotations, an OCI
artifact, or an external loader.
How does it reach the database? Either as versioned migration files that are reviewed, committed, applied in order and recorded in a revision table, or as a direct apply that computes the difference and executes it after approval.
| Versioned files | Direct apply | |
|---|---|---|
| You write the migration | migrations create, write the SQL, hash, up — Quick start: versioned migrations |
— |
| Ptah derives it from a desired schema | migrations generate, then the same hash and up |
schema apply |
The empty cell is not an omission: a direct apply computes a difference, and
with no description of the schema you want there is nothing to compute one
against. Everything else combines, and neither row is a reduced form of the
other — a project can run for years on migrations it writes by hand and use
every verb below except generate and plan.
How Ptah models the two workflows
Section titled “How Ptah models the two workflows”Versioned migrations
Section titled “Versioned migrations”The native ptah migrations namespace owns this workflow, and only two of its
verbs read a desired schema. create scaffolds an empty *.up.sql/*.down.sql
pair for you to fill in; generate writes the same pair from the difference
between a desired schema and a database. From there the lifecycle is identical:
hash seals the directory in the ptah.sum integrity file, up applies
pending files in order and records each one in the revision table, down
replays the committed rollback files, and status, validate, lint,
import, checkpoint and the history verbs never ask what the schema should
look like.
ptah migrations generate \ --schema-file schema.sql \ --db-url "$DATABASE_URL" \ --migrations-dir ./migrations \ --name add_orders
ptah migrations hash --dir ./migrations
ptah migrations up \ --db-url "$DATABASE_URL" \ --migrations-dir ./migrations \ --verify-sumThe migration directory lives in your repository, so every change is code-reviewed as SQL, and every environment replays the same files in the same order. Quick start: versioned migrations runs the hand-written half of this loop end to end.
Direct schema changes
Section titled “Direct schema changes”If you arrived looking for a “declarative” workflow, this is it. Ptah does not use that word as the name, because ptah migrations generate reads the same desired schema and is not in this group: “declarative” names where a change came from, not how it lands. Direct schema changes sets out the distinction.
Native commands cover the whole loop: ptah db read prints a live schema, ptah schema compare shows how it differs from the desired schema, ptah schema drift turns that difference into a check that exits non-zero when the database has diverged, and ptah schema apply applies the planned change directly.
ptah schema drift --schema-file schema.sql --db-url "$DATABASE_URL"
ptah schema apply \ --db-url "$DATABASE_URL" \ --schema-file schema.sql \ --dry-runWith --dry-run, ptah schema apply prints the planned SQL under a Planned schema changes: heading and stops. Without it, the command shows the same plan and asks for approval before executing (--auto-approve skips the prompt). To separate review from execution, ptah schema plan writes a fingerprinted local plan file, and ptah schema apply --plan <path> runs it only while the database still matches the plan’s recorded starting state.
Consequences and tradeoffs
Section titled “Consequences and tradeoffs”| Question | Versioned migrations | Direct schema changes |
|---|---|---|
| Whether a desired schema is required | No, except for generate and plan |
Yes |
| What gets reviewed | Migration SQL committed to the repository | Planned SQL at apply time, or a saved plan file |
| What records history | Migration directory, ptah.sum, revision table |
Nothing; the live database is the only state |
| How rollback works | ptah migrations down replays committed down files |
A new diff toward the schema you want back |
| Where it fits | Shared and production databases, teams, CI gates | Prototypes, local development, single-owner databases |
| Main commands | ptah migrations ... |
ptah schema drift, ptah schema apply |
The versioned workflow costs you a migration directory to maintain and the discipline of hashing and reviewing it. In exchange, changes are auditable, rollback is a committed file rather than an improvisation, and hashed directories verify integrity before anything touches a database, and --verify-sum additionally requires the sum file to exist. The direct workflow removes the file overhead and iterates fastest, but the apply-time approval is its only gate, and there is no history to replay or audit.
A common hybrid uses both: iterate with ptah schema apply against a disposable local database, then run ptah migrations generate against a database at the released state so the reviewed migration file — not the ad-hoc changes — is what reaches shared environments. In either model, ptah schema drift works as a pipeline guard that fails when a database no longer matches the desired schema.
Where each workflow appears
Section titled “Where each workflow appears”- The versioned lifecycle — plan, generate, apply, roll back, verify — lives in Versioned migrations.
- The direct workflow — inspect, compare and drift, apply — lives in Direct schema changes, with direct application on Apply directly.
- Bringing a database that already exists into either workflow is covered in Adopt an existing database.