Database migrations
Auto-migration on boot
Section titled “Auto-migration on boot”By default, z4j runs pending migrations on start. This is idempotent and safe to leave on.
Disable with Z4J_AUTO_MIGRATE=false – then run migrations manually.
Manual migration
Section titled “Manual migration”docker run --rm \ -e Z4J_DATABASE_URL=... \ z4jdev/z4j:latest z4j migrate upgrade headOr inside an existing container:
docker exec -it z4j z4j migrate upgrade headStatus
Section titled “Status”z4j migrate currentShows the currently applied revision. Use z4j migrate history for the full revision list.
Bidirectional schema policy
Section titled “Bidirectional schema policy”Every schema migration is bidirectional. Every
revision ships a working downgrade() function and a CI integration
test (TestMigrationRoundTrip in tests/integration/test_migration_pg.py)
that proves the round-trip cleanly against real Postgres 18:
upgrade head -> seed sample data -> downgrade base -> verify schema is gone -> upgrade head -> verify smoke insert worksThe CI job blocks releases. If the round-trip fails, the bidirectional claim is no longer true and we revert the breaking migration before tagging.
What’s guaranteed
Section titled “What’s guaranteed”- Schema bidirectional: every additive migration (add column, add
table, add index, add trigger) round-trips cleanly.
pip installforward and back across patch / minor lines works at the schema level without manual intervention. - Postgres-only artefacts (partitioned tables, GIN/partial indexes, ENUM types, trigger functions) are dropped on downgrade by their corresponding helper. Tested explicitly.
What’s NOT guaranteed
Section titled “What’s NOT guaranteed”- Data preservation through downgrade: downgrading drops tables
by design. Your row data is gone after
alembic downgrade base, not preserved across the round-trip. The contract is bidirectional schema, not bidirectional data. For data-preserving rollback use backup-restore - take a snapshot before upgrading, restore if something is wrong. - Destructive migrations: a future migration that intentionally
drops a column with data CANNOT be reversed by
downgrade()without that data being elsewhere. These migrations declare themselves withirreversible = Truein theircompatdict and exitalembic downgradecleanly with an operator-facing message pointing at the backup-restore workflow.
Postgres extensions are intentionally not dropped
Section titled “Postgres extensions are intentionally not dropped”The migration installs pgcrypto, citext, and pg_trgm.
Downgrade does NOT drop them, by design: extensions are per-database
and dropping one could break other applications sharing the same DB.
Leaving them installed costs nothing.
Multi-replica
Section titled “Multi-replica”For N-replica deploys, run migrations from a one-shot job before rolling out the new brain image:
# kubernetes exampleapiVersion: batch/v1kind: Jobmetadata: name: z4j-migratespec: template: spec: containers: - name: migrate image: z4jdev/z4j:latest command: ["z4j", "migrate", "upgrade", "head"] env: [...] restartPolicy: NeverThen update the Deployment’s image.
Schema reference
Section titled “Schema reference”See database schema for the authoritative table + column list.
Underlying tool
Section titled “Underlying tool”Alembic (SQLAlchemy 2.0). Revisions live in packages/z4j/backend/alembic/versions/. Each revision includes a descriptive message and never loses data without explicit, commented-out intent.