ADR-009: Use Goose for Database Migrations
We chose goose as the database migration tool for managing schema versioning, up/down migrations, and tracking applied changes.
Status
Section titled “Status”Accepted
Context
Section titled “Context”Phase 2 introduces the initial database schema — nine tables covering businesses, events, users, roles, menus, and related entities. We need a migration tool that supports:
- Versioned up and down migrations in plain SQL.
- Tracking which migrations have been applied.
- Running migrations programmatically from Go at application startup.
- A CLI for manual migration management during development.
Three options were considered:
- goose (
github.com/pressly/goose/v3) — SQL or Go migration files, works directly withdatabase/sql, supports Goembed.FS, built-in CLI. - golang-migrate (
github.com/golang-migrate/migrate/v4) — SQL migration files, requires separate database driver and source driver packages. - Raw SQL files without a runner — manual execution, no version tracking.
Decision
Section titled “Decision”We use goose because:
- It works directly with
database/sqland requires only a single Go dependency. The project already registers the pgx driver, so goose needs no additional database driver package. - It supports
embed.FS, which lets us bundle migration files into the compiled binary. This means the production container does not need a separatemigrations/directory at runtime. - The Go API is clean and minimal:
goose.SetDialect("postgres")followed bygoose.Up(db, dir). This integrates naturally into the existingdatabasepackage. - The built-in CLI (
goose -dir server/migrations up/down/status) is useful during development for manual migration management and status inspection. - It tracks applied migrations in a
goose_db_versiontable.Up()is idempotent — it only applies pending migrations and skips already-applied ones. - golang-migrate was rejected because it requires separate packages for the database driver (
database/pgx/v5) and file source (source/file), adding dependency complexity without additional benefit. Its Go API is also less ergonomic for our use case. - Raw SQL without a runner was rejected because it provides no version tracking, no rollback automation, and no way to programmatically apply migrations at startup.
Consequences
Section titled “Consequences”github.com/pressly/goose/v3is added as a Go dependency inserver/go.mod.- Developers install the goose CLI via
go install github.com/pressly/goose/v3/cmd/goose@latestfor manual migration management. - Migration files live in
server/migrations/and follow goose’s naming convention (sequential numbering with up/down suffixes). - Migrations auto-run at API startup. This keeps development and production schemas in sync without manual steps.
- Goose creates and manages a
goose_db_versiontable in the database. This table must not be modified manually. - If we later need Go-based migrations (data transformations, backfills), goose supports
.gomigration files alongside.sqlfiles without changing the tool.