TRUSTIVAN’s schema is managed by golang-migrate. There are nine migrations, each with an up and a down.

They run automatically

Migrations are applied during application startup, before anything else is built. Both cmd/api and cmd/worker do this, so docker compose up on an empty database produces a working schema with no separate step. Running them at startup rather than as a deployment step is a decision about failure modes: a process that finds a schema it does not understand should not start, and a schema that lags the binary is exactly the situation that produces confusing runtime errors rather than a clear startup one. Concurrent starts are safe. The runner takes a PostgreSQL advisory lock around the whole migration run, so several instances starting at once serialise rather than racing, and applying nothing when the schema is already current is a no-op.

Running them by hand

Useful when you want to prepare a database before deploying the binaries, or to see what a migration does before it runs unattended.
Or directly:
cmd/migrate reads two settings and nothing else: Inside the container the files are at /app/migrations, copied in by the Dockerfile’s final stage.

Rolling back

go run ./cmd/migrate down N — or make db-migrate-down STEPS=N — reverses the last N applied migrations. The count is mandatory; the command prints what each down file drops before running, --dry-run stops there, and a rollback that would drop the audit trail or an identity table is refused without --force-drop-audit. It exists for development and for tests. Nothing calls it at startup. Every migration having a down is what makes an upgrade reversible in principle, but a down returns the schema, not the data it dropped. Before upgrading a deployment that holds anything you care about, back up PostgreSQL first. See Upgrades.

Adding a migration

Two files, numbered in sequence, up and down:
Write the down at the same time as the up, while you still remember what the up did. A migration whose down is written months later during an incident is a migration with no down.

When startup fails on a migration

The process exits with running migrations: … and does not serve traffic. Three common causes:
  • A dirty schema version. golang-migrate marks the version dirty when a migration fails part-way. Resolve the partial state manually, then clear the dirty flag in schema_migrations before restarting.
  • Insufficient privileges. The role in DATABASE_URL needs DDL rights on the database, not just DML.
  • The wrong MIGRATIONS_PATH. Running cmd/migrate from a directory other than backend/ resolves the default relative path somewhere it will not find the files.
See Troubleshooting for symptoms that appear after startup rather than during it.