schemagen
Schema-first CRUD generator with a canonical database snapshot workflow.
Workflow TL;DR
schemagen config init --template=<local|docker|cloud> # defaults to db_url: ${DATABASE_URL}
schemagen config verify
schemagen config verify --connectivity # optional (docker/network/db probes)
schemagen schema snapshot
schemagen schema fmt # alias: schemagen schema verify
One-command workflow wrapper (examples)
schemagen intentionally does not own migrations (that wiring is repo-specific), but many teams still want a single reproducible command that runs snapshot + sqlc + codegen + tests.
Copy/paste the wrapper from examples/workflow/:
examples/workflow/scripts/install-tools.sh: optional repo-local tool install into ./.bin/
examples/workflow/scripts/generate.sh: orchestration script (generate and generate-check)
examples/workflow/Justfile, Taskfile.yml, Makefile: thin wrappers that call the script
Example (from your repo root):
just -f examples/workflow/Justfile install-tools
just -f examples/workflow/Justfile generate
just -f examples/workflow/Justfile generate-check
Windows: run the scripts under WSL2 or Git Bash.
.schemagen/schemagen.yaml is required. The loader stops the CLI with a clear error if the file is missing—run schemagen config init first, review the file into source control, and treat it as the single source of truth for generator settings.
Configuration precedence
Every key in the generated YAML documents its precedence:
.schemagen/schemagen.yaml
- Environment variable (
SCHEMAGEN_*, e.g., SCHEMAGEN_DB_URL, SCHEMAGEN_PG_MODE, SCHEMAGEN_SCHEMA_OUTPUT, ...)
- CLI flag (temporary overrides only)
Passwords or secrets should stay in env vars. schemagen config print redacts the connection string and shows the merged view (file + env + flags) for debugging/CI.
db_url also supports ${VAR} expansion so you can commit db_url: "${DATABASE_URL}" and keep the secret in your environment / secret manager.
Templates (schemagen config init --template=…)
| Template |
Description |
local |
Uses the pg_dump binary installed on the host. You must point db_url at the host/port where Postgres listens (no automatic rewriting to host.docker.internal). |
docker |
Runs pg_dump via docker run. Provide a reachable db_url (service hostname, not localhost) and configure pg_docker.network / pg_docker.container based on docker-run vs docker-exec. |
cloud |
Starts from a cloud-hosted DB connection string (RDS/Cloud SQL/etc.) and leaves docker fields empty. |
Every template seeds schema_output with .schemagen/schema/schema.sql. If you prefer a different location (for example db/schema.sql or schema.sql in the repo root), edit the generated YAML (or set SCHEMAGEN_SCHEMA_OUTPUT) after config init.
Config commands
schemagen config init
- Non-interactive: you must pass
--template=<local|docker|cloud>. The scaffold defaults to db_url: "${DATABASE_URL}" so secrets stay in env vars / CI secrets (use --db-url only as an escape hatch).
- Auto-detects whether
pg_dump is on PATH. If it is, the default pg_mode becomes local; otherwise it prefills pg_mode: docker-run. Edit the YAML if the defaults don’t match your topology.
- Refuses to overwrite unless
--force.
- Use
--verify if you want to run schemagen config verify immediately after writing; by default the command just writes the YAML so bootstrapping/offline setups aren’t blocked.
- Exports default pongo2 templates into
.schemagen/templates (safe to edit/override).
Example (docker-run hitting a compose network):
schemagen config init \
--template docker
# If you use Docker Compose, `pg_docker.network` is typically <project>_default (schemagen tries to prefill it).
schemagen config verify
- Always runs a fast lint pass (pure YAML validation, no docker/network). CI should run this command without flags.
- Pass
--connectivity when you actually want docker/network/database probes. Combine with --skip-docker if your environment (e.g., CI) cannot talk to Docker.
- Connectivity checks reuse the same heuristics as
schema snapshot: linux defaults to --network host, macOS/Windows rewrite localhost to host.docker.internal, docker-run connectivity uses pg_isready inside the container, and docker-exec ensures the target container is already running.
- Logs the verified plan (sanitized DB URL, pg mode, schema output path) so you can see exactly what will run.
Example:
schemagen config verify --connectivity
schemagen config verify --connectivity --skip-docker # when Docker isn’t available
schemagen config print
- Prints the merged config with secrets redacted—useful for CI or support tickets.
Schema commands
schemagen schema snapshot
- Always reads
.schemagen/schemagen.yaml; flags like --pg-mode, --pg-docker, and --out still exist for automation but are hidden from --help.
- Uses deterministic
pg_dump --schema-only --no-owner --no-privileges --format=p --no-comments --quote-all-identifiers --no-password.
- Atomic writes: stdout streams into a temp file, the header is rewritten, and we rename the temp file to the configured snapshot path (
schema_output). Failures leave the previous snapshot untouched.
- Secrets never hit stdout/logs: passwords in URLs or DSNs are redacted and passed via
PGPASSWORD.
- Docker heuristics: on Linux, docker-run defaults to
pg_docker.network: host; on macOS/Windows, schemagen rewrites loopback hosts to host.docker.internal when running inside docker-run so the container can reach the host Postgres instance.
schemagen schema fmt (alias schemagen schema verify)
- Rewrites only the header + trailing newline of the snapshot (
schema_output). The schema body stays untouched.
- Handy before committing a hand-edited schema or when regenerating without re-running
pg_dump.
Generate commands
schemagen generate init
Why it exists (business rationale):
- Faster onboarding: turns an existing schema snapshot into an editable generation contract.
- Avoids manual YAML boilerplate: produces a consistent starting point.
- Prevents accidental API exposure: resources are disabled by default until explicitly enabled.
- Enforces pagination safety: global limits exist even before any code is generated.
- Prevents leaking secrets: sensitive columns (password/token/digest/hash patterns) are auto-excluded from transport exposure.
- Keeps generated code isolated: output directories are specified up-front to reduce merge conflicts and protect hand-written code.
Recommended flow
| Step |
Command |
Notes |
| 1 |
schemagen config init --template=<local|docker|cloud> |
Writes .schemagen/schemagen.yaml with db_url: "${DATABASE_URL}" by default. |
| 2 |
schemagen config verify |
Lints the config (run with --connectivity when you want docker/network/database probes). |
| 3 |
schemagen schema snapshot |
Dumps .schemagen/schema/schema.sql. |
| 4 |
schemagen schema fmt |
Keeps the header/tail canonical. |
Taskfile helpers
task config-init expects SCHEMAGEN_TEMPLATE (local, docker, or cloud).
task config-verify wraps schemagen config verify.
task schema runs the snapshot+fmt flow via the locally built binary.
Make/Just equivalent:
config-init:
schemagen config init --template local
config-verify:
schemagen config verify
schema:
schemagen schema snapshot
schemagen schema fmt
Usage examples
Local machine with pg_dump
schemagen config init --template local
export DATABASE_URL="postgres://user:pass@localhost:5432/app?sslmode=disable"
schemagen config verify
schemagen schema snapshot
schemagen schema fmt
Docker / docker-compose
schemagen config init --template docker
export DATABASE_URL="postgres://USER:PASSWORD@postgres:5432/DB?sslmode=disable"
# Edit .schemagen/schemagen.yaml and set pg_docker.network to your compose network (e.g., myapp_default).
schemagen config verify
schemagen schema snapshot
schemagen schema fmt
Cloud SQL / managed Postgres
schemagen config init --template cloud
export DATABASE_URL="postgres://USER:PASSWORD@your-instance.rds.amazonaws.com:5432/DB?sslmode=disable"
schemagen config verify
schemagen schema snapshot
schemagen schema fmt
The schema snapshot (default: .schemagen/schema/schema.sql) remains the canonical artifact—migrations capture history, but this snapshot is the “truth” your generators consume.