This guide explains how to build and run the compiled migrations binary by hand — useful for CI/CD pipelines, Docker builds, debugging, or any situation where you can't use makemigrations migrate.
For day-to-day use prefer
makemigrations migrate <command>which handles all of this automatically.
The migrations/ directory is a separate Go module with its own go.mod. Several environment concerns can cause a plain go build to fail:
| Problem | Symptom |
|---|---|
Parent go.work doesn't list migrations/ |
main module does not contain package …/migrations |
go.work has a partial version like go 1.25 |
go: downloading go1.25 (linux/amd64): toolchain not available |
go.sum is stale after a makemigrations upgrade |
missing go.sum entry for module providing package … |
GOTOOLCHAIN=local with an older system Go |
go.mod requires go >= 1.24 (running go 1.22.2; GOTOOLCHAIN=local) |
When a go.work file exists in a parent directory it overrides module resolution for the whole workspace, but migrations/ is typically not listed in it. Disable it for the build:
GOWORK=off go build -o migrations/migrate ./migrations/Or cd into the directory first:
cd migrations
GOWORK=off go build -o migrate .If you have just upgraded makemigrations or pulled a new version, the migrations/go.sum may be stale. Run go mod download before building:
cd migrations
GOWORK=off go mod download
GOWORK=off go build -o migrate .If your parent go.work or go.mod requires a Go version that isn't installed locally (e.g. go 1.25), the Go toolchain may try to download it. Pin the toolchain to the installed binary using GOTOOLCHAIN:
cd migrations
# Use the exact version that is already installed (e.g. go1.25.7)
GOWORK=off GOTOOLCHAIN=$(go env GOVERSION) go build -o migrate .go env GOVERSION returns the full patch version (e.g. go1.25.7), which Go recognises as a locally-available toolchain and does not attempt to download.
If your parent go.mod has a replace directive pointing to a local copy of makemigrations, you can expose it to the migrations/ build via a temporary go.work:
# Create a temporary workspace that includes both modules
cat > /tmp/migrations.work <<EOF
go 1.25.7
use /absolute/path/to/your-project/migrations
use /absolute/path/to/makemigrations
EOF
GOWORK=/tmp/migrations.work go build -o migrations/migrate ./migrations/
makemigrations migratedoes all of this automatically by detecting the local replace directive in parentgo.modfiles.
# Simple: no workspace, no toolchain issues
cd migrations && GOWORK=off go build -o migrate .
# With go.sum sync
cd migrations && GOWORK=off go mod download && GOWORK=off go build -o migrate .
# Pin toolchain to installed version
cd migrations && GOWORK=off GOTOOLCHAIN=$(go env GOVERSION) go build -o migrate .
# From project root (adjust path as needed)
GOWORK=off go build -o migrations/migrate ./migrations/The binary reads database connection details from environment variables wired up in migrations/main.go. The generated main.go only reads DB_TYPE and DATABASE_URL:
export DATABASE_URL="postgresql://user:pass@localhost/mydb"
export DB_TYPE="postgresql" # optional, defaults to "postgresql"
./migrations/migrate up
./migrations/migrate status
./migrations/migrate fake 0001_initial
./migrations/migrate down --steps 1If you need to support individual DB_HOST / DB_PORT / DB_USER / DB_PASSWORD / DB_NAME fields, edit migrations/main.go to populate them:
app := m.NewApp(m.Config{
DatabaseType: m.EnvOr("DB_TYPE", "postgresql"),
DatabaseURL: m.EnvOr("DATABASE_URL", ""),
DBHost: m.EnvOr("DB_HOST", "localhost"),
DBPort: m.EnvOr("DB_PORT", "5432"),
DBUser: m.EnvOr("DB_USER", "postgres"),
DBPassword: os.Getenv("DB_PASSWORD"),
DBName: m.EnvOr("DB_NAME", "mydb"),
DBSSLMode: m.EnvOr("DB_SSLMODE", "disable"),
})DATABASE_URL takes priority over the individual fields when both are set.
- name: Apply database migrations
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
run: |
cd migrations
GOWORK=off go mod download
GOWORK=off go build -o migrate .
./migrate upOr, if makemigrations is installed as a tool:
- name: Apply database migrations
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
run: makemigrations migrate up- migrate command — full reference for all binary subcommands
- init command — bootstrap the
migrations/directory - makemigrations command — generate
.gomigration files