Configuration
Configuration precedence is:
| Rank | Source |
|---|---|
| 1 | Explicit CLI flags |
| 2 | Environment variables |
| 3 | atlas.hcl selected environment |
| 4 | ptah.yaml selected environment |
| 5 | Built-in defaults |
A PTAH_<FLAG> value must parse as the corresponding flag type. Ptah rejects a
malformed value before argument validation, command hooks, or database work
begins. For example, PTAH_DRY_RUN=notabool fails with
invalid boolean value "notabool" for PTAH_DRY_RUN instead of running with
the default false value. An explicit CLI flag wins without reading its
environment twin, including when that environment value is malformed. An empty
value remains unset for every flag type except boolean, where it is rejected;
see Boolean environment variables.
The one flag with no environment variable
Section titled “The one flag with no environment variable”Approval flags do not bind to a PTAH_* variable. They have to be typed, or
written into a command line somebody wrote:
| flag | on |
|---|---|
--auto-approve |
ptah schema apply, ptah db drop-all, ptah mcp, ptah assist, ptah assist explain, ptah assist context |
--allow-database-inspect |
ptah mcp, ptah assist, ptah assist explain, ptah assist context |
Every other flag in the native CLI binds to one.
That is deliberate, and it is the reason every other flag can safely have one: a container, a CI job, or a Kubernetes Job can be configured entirely through the environment, and none of that configuration can turn a review step into an unattended apply. A variable exported once in a shell profile would otherwise approve every later run in that shell.
A Kubernetes Job passes it in args: rather than env:, which is the same
distinction: what the workload is versus what it is configured with.
Boolean environment variables
Section titled “Boolean environment variables”Every boolean PTAH_* variable follows one rule, whether it is a flag’s
environment twin or a feature toggle read directly:
| State | Result |
|---|---|
| Not set | The documented default |
| Set to a valid boolean | Parsed and honored |
| Set to anything else | The command fails before doing any work |
The accepted spellings are Go’s strconv.ParseBool spellings:
true: 1 t T true True TRUEfalse: 0 f F false False FALSENothing is trimmed before parsing. " true", "true " and an exported empty
value are all rejected, because a quoting or expansion mistake in a YAML
manifest, a CI environment file or a systemd unit is exactly the configuration
error this rule exists to surface. The refusal names the variable and the raw
value:
Error: invalid boolean value "maybe" for PTAH_POSTGRES_INSPECT_ALL_ROLESThe refusal comes before the command connects to a database, writes a migration
file, applies schema changes, touches revision state, or produces a
machine-readable result on standard output. It also comes before the command’s
own early returns: an invalid value is rejected even on a run that would never
have consulted it, such as a migrate lint that already names --latest, or a
schema apply whose --exclude selectors all match.
A command only validates the variables it owns. A malformed PostgreSQL
inspection variable does not break an unrelated SQLite command or
ptah version.
Non-boolean PTAH_* variables are unchanged: an empty value still reads as
unset for them.
Atlas CE strict compatibility
Section titled “Atlas CE strict compatibility”PTAH_ATLAS_STRICT_COMPAT is a ptah-compat-only boolean policy selector. It
defaults to false, which keeps the complete Pro-like and best-effort
compatibility surface. Set it to true only for Atlas Community Edition oracle
or conformance runs:
PTAH_ATLAS_STRICT_COMPAT=1 ptah-compat migrate validate --dir file://migrationsThe process resolves the selector before command construction. In strict mode,
generic PTAH_<FLAG> environment twins are not installed because Atlas CE does
not expose them. A present extension variable, including one whose command flag
is absent from the strict tree, is rejected rather than ignored. False values
for known boolean extension toggles remain valid; malformed values retain the
normal boolean diagnostic.
The check is based on Ptah’s actual flag bindings and feature-toggle registry,
not on the PTAH_ prefix alone. An ordinary value that an atlas.hcl reads
through getenv, such as PTAH_DATABASE_PASSWORD, remains available in strict
mode. Naming project inputs with that prefix does not turn them into Ptah
features.
Strict mode also applies its object inventory to live schemas. schema inspect,
schema apply, schema diff, and schema clean refuse a Pro-only object
before rendering, comparison, or mutation instead of copying CE behavior that
could omit, miscompare, or destroy it. On schema clean, the CE-registered
--dry-run and --format flags remain gated. The diagnostic names
ptah-compat and tells the operator to unset the selector; Ptah never directs
the operator to install Atlas. Leave strict mode off for Ptah’s complete
cleanup, inspection, diff, and apply capabilities.
Strict inspection omits PostgreSQL’s server-installed plpgsql extension and
baseline PUBLIC USAGE grant from rendered output. Strict cleanup executes the
validated plan after confirmation. On PostgreSQL it locks the planned tables,
revalidates the live inventory, compares the rebuilt cleanup plan with the
confirmed plan, and refuses drift before the first drop. Full mode preserves
the complete reader output and its established unscoped writer path.
The cleanup check covers the writer’s complete destruction inventory. That includes PostgreSQL procedures, aggregates, foreign tables, collations, default privileges, and dependent objects such as triggers that disappear with a table without appearing as a separate cleanup plan line.
Strict schema workflows also refuse YAML sources and an authored schema apply
lint policy that the CE execution path cannot enforce. Commands that execute,
convert, or replay migration bodies refuse Atlas txtar, every Ptah directive,
and SQL templates; checksum-only reads preserve those bytes. All inputs remain
available in the default, complete compatibility profile.
Which variables strict mode gates is derived from Ptah’s boolean-variable registry, not from a list kept beside it. Each variable states at its single declaration site whether strict mode gates it or retains it, so a variable added without stating anything is refused rather than ignored, and there is no second list to forget.
These opt-in correctness controls remain available because they do not add an
Atlas capability: PTAH_ALLOW_EXTERNAL_TEST_COMMAND,
PTAH_ALLOW_NONINTERACTIVE_EDIT,
PTAH_ATLAS_ALLOW_UNMATCHED_EXCLUDE, PTAH_HCL_STRICT_REDECLARATIONS,
PTAH_SQLITE_ALLOW_UNREGISTERED_VIRTUAL_MODULE,
PTAH_SQLITE_ALLOW_VIRTUAL_TABLE_DROP and PTAH_STRICT_DIR_QUERY.
PTAH_ALLOW_EXTERNAL_TEST_COMMAND authorizes an external step in a
.test.hcl case, which runs a program on the machine executing the suite.
Ptah refuses one by default and this variable restores it, so it adds no capability the pinned community binary does not already have; gating it would leave a conformance run unable to exercise the step at all.
The program is executed as an argument vector with no shell, and the run is refused before any database is provisioned when the variable is absent.
PTAH_ALLOW_NONINTERACTIVE_EDIT permits a scripted editor in a non-interactive
process; it does not add an editor or migration capability.
PTAH_SQLITE_ALLOW_VIRTUAL_TABLE_DROP restores the DROP TABLE the pinned
community binary plans for a SQLite virtual table anyway, and
PTAH_SQLITE_ALLOW_UNREGISTERED_VIRTUAL_MODULE lifts a refusal that is Ptah’s
own, since the binary has no notion of a module this build cannot classify and
plans the drops regardless. Gating either would make strict mode the one place
Ptah is stricter than the binary it matches. Every other declared
boolean is gated, and a malformed value for any of them fails the process
whatever its classification. Native ptah does not read
PTAH_ATLAS_STRICT_COMPAT.
Project-file merging preserves source presence. For a supported field, an
explicitly present value replaces the lower-precedence value instead of being
treated as absent. This includes an empty string, zero, false, or an empty
list when the field accepts that type. Thus atlas.hcl wins over ptah.yaml,
while environment variables and explicit CLI flags still win.
After project sources are merged, a command applies its built-in default only
when a field is absent. An explicitly present empty or zero value instead
reaches normal validation. Fields that do not accept empty values, including
Atlas format templates, fail during parsing or command validation.
Use ptah.yaml for Ptah-owned configuration and the supported atlas.hcl
subset for Atlas-compatible project config. The supported Atlas subset includes
local variable defaults and Atlas-style --var name=value overrides,
locals, getenv, file, fileset, data.hcl_schema, data.sql,
data.external, data.runtimevar, and data.template_dir. Recognized project
data sources are evaluated only when the selected config references them.
Supported Atlas env blocks can also set schema.src, schema.mode, format,
and local diff policy defaults for the ptah-compat binary’s commands.
ptah-compat migrate apply expands env for_each collections and applies each
selected database target sequentially.
Ptah reads each selected project config once per command and converts it to a
typed configuration value. Migration database settings and online-DDL policy
therefore cannot come from different generations of a concurrently replaced
ptah.yaml. When an Atlas-compatible adapter delegates to a native command
that also consumes project configuration, the adapter passes the merged typed
snapshot instead of letting that command reopen either project file. This
currently applies to migrate down; other adapters evaluate Atlas project
configuration once and map supported values to explicit native command
arguments. An explicit --config path must exist; the conventional
./ptah.yaml is optional.
--config takes a ptah.yaml file. Pointing it at an atlas.hcl is refused by
name rather than reported as a YAML parse failure: the Atlas project config is
discovered as ./atlas.hcl and selected with --env.
Every native command that accepts --env also accepts --var name=value, which
supplies a value for an atlas.hcl variable block that declares no default.
The flag is repeatable, and repeating one name builds a list(string). This is
the flag the evaluator names when a variable cannot be resolved:
$ ptah schema compare --env local --schema-file schema.sqlerror: atlas.hcl variable "dburl" requires a default or --var dburl=value$ ptah schema compare --env local --schema-file schema.sql --var dburl=sqlite://app.dbenv:// references resolve on the ptah-compat --to and --from flags. The
native --schema-file does not resolve them and says so, naming env:// and,
for an attribute outside src, schema.src, url, dev, migration, and
migration.dir, naming the attribute.
For Atlas-compatible commands, plain local schema paths, relative file://
schema URLs, and relative migration.dir values declared in atlas.hcl resolve
relative to the directory containing that atlas.hcl file. Explicit CLI path
flags such as --to, --from, and --dir keep CLI semantics and resolve
relative to the process working directory unless they are absolute.
A project-configured migration.dir, whether relative or absolute, must remain
inside the directory containing atlas.hcl after symbolic-link resolution.
Parent traversal that resolves outside, an absolute outside path, and a
symbolic-link escape fail as outside allowed root. An explicit CLI --dir is
operator-owned and retains the CLI path behavior above.
Two files, one project model
Section titled “Two files, one project model”ptah.yaml and the supported atlas.hcl subset are both native Ptah project
languages. Neither is an import format for the other: each is parsed into the
same project-config model, and every native ptah verb that reads project
configuration reads either file. A project written in HCL needs no conversion
to YAML to be operated by native Ptah, and one written in YAML needs no HCL.
What differs is reach, not standing. The Atlas subset is bounded by what the
compatibility profile covers, so a setting only Ptah has is expressed in
ptah.yaml; Ptah’s own HCL extensions live in the schema language rather than
the project file, and are listed in HCL schema. A project
using one of those is no longer a project Atlas reads.
Both files can be present at once, and are merged by the precedence above. Adopting an Atlas project walks the path that uses this.
A minimal project config
Section titled “A minimal project config”A native command selects an environment with --env, and reads it from either
project file: ./ptah.yaml, or the supported subset of ./atlas.hcl. The two
below are equivalent, and ptah migrations status --env dev answers the same
way beside each.
env: dev: url: sqlite:////tmp/ptah-dev.db migration: dir: ./migrationsenv "dev" { url = "sqlite:////tmp/ptah-dev.db"
migration { dir = "file://migrations" }}Run with the named environment:
ptah migrations status --env devptah migrations up --env dev --verify-sumThe two files are not interchangeable everywhere: --config names a
ptah.yaml and refuses an atlas.hcl by name, because the Atlas project
config is discovered as ./atlas.hcl and selected with --env. Where both
exist, atlas.hcl wins — see the precedence table above.
If a command needs project values from a config file with multiple
environments, pass --env. Commands whose required inputs are all explicit do
not need to select an environment. Ptah fails instead of guessing only when
project values are required and the environment remains ambiguous.
Operational settings
Section titled “Operational settings”Project config can also define timeouts, revision table layout, migration directory format, transaction mode, backup destinations, pre-flight hooks, webhooks, lint defaults, and online-DDL policy.
The dev value supplies the disposable database for migration generation and
the shadow rehearsal that migrations down performs before touching its target.
| Setting area | Example keys |
|---|---|
| Database target | url, src, schema.src, dev, schemas |
| External desired schema | external_schema.program, external_schema.format, external_schema.working_dir, external_schema.env |
| Migration directory and revisions | migration.dir, migration.format, migration.revisions_table, migration.revision_format |
| Safety and operations | migration.pre_up_hook, migration.pg_dump_to, migration.webhook, migration.exec_order, migration.tx_mode |
| Lint defaults and policy | lint.dialect, lint.disabled-rules, lint.latest, lint.git.base, lint.destructive.error, lint.concurrent_index.error |
| Online DDL | online_ddl.tool, online_ddl.threshold_rows, online_ddl.args, online_ddl.fallback |
Diff policy (native migrations generate) |
diff.skip: [drop_table, drop_column, drop_index, drop_enum], diff.concurrent_index, diff.concurrent_index_drop |
| Atlas-compatible output | format.schema.inspect, format.schema.apply, format.schema.clean, format.schema.diff, format.migrate.apply, format.migrate.diff, format.migrate.lint, format.migrate.status |
| Atlas-compatible diff policy | diff.skip.drop_table, diff.concurrent_index.create, diff.concurrent_index.drop |
The native diff block shapes what ptah migrations generate emits: diff.skip
lists destructive change kinds (drop_table, drop_column, drop_index,
drop_enum) to omit — a -- SKIP: ... comment is written in their place — and
diff.concurrent_index: true requests CREATE INDEX CONCURRENTLY for new
indexes (PostgreSQL, capability-gated), while
diff.concurrent_index_drop: true requests DROP INDEX CONCURRENTLY for
standalone index removals under the same gate. A skipped change is never
emitted, so it never trips the --check-destructive gate. A selected environment’s
diff.skip replaces the top-level list; an explicit empty list clears all
inherited skip kinds.
drop_table also reaches the comparison, not only the rendered SQL. Ptah’s
SQLite virtual-table guard refuses a comparison for the DROP TABLE it predicts,
and a project that skips table drops has already deleted that statement, so the
refusal does not fire — see SQLite. It is the only
skip kind that changes what a comparison refuses, and it changes nothing about
what a comparison computes.
The Atlas-compatible command tree lives in the separate ptah-compat binary,
the drop-in replacement for scripts that expect Atlas-style root commands.
Atlas project flags such as --config, -c, --env, and repeated
--var name=value belong to that tree only; it is not a separate
configuration surface.
External desired schema
Section titled “External desired schema”Use external_schema when an ORM, framework, or generator owns the desired
schema:
external_schema: program: [".venv/bin/atlas-provider-sqlalchemy", "--path", "./models", "--dialect", "postgresql"] format: sql working_dir: ./app env: ["APP_ENV=dev"]program is an explicit argument list and is executed without a shell.
format accepts sql (the default), hcl, or yaml. The block supplies the
desired schema to native schema render, schema compare, schema drift,
migrations plan, and migrations generate commands when --schema-cmd is
not set. An explicit command and its --schema-format take precedence;
--schema-cmd= disables the configured source. --env selects an
environment-scoped block. Empty command output is rejected. PATH and PWD
cannot be overridden through external_schema.env; use an explicit executable
path and working_dir.
Auto-discovered configuration never executes a program implicitly. Pass
--allow-external-schema to use the configured block. An explicit
--schema-cmd is already an opt-in and does not require that additional flag.
Relative working_dir values are constrained to the current working directory
after symlink resolution. Ptah bounds output, redacts secrets and terminal
control characters from stderr and parser diagnostics, and cleans up descendant
processes.
Continue with Atlas project config for the supported
atlas.hcl subset.