The migrate binary is the runtime component of the Go migration framework. It is compiled from the migrations/ directory in your project and is the primary way to apply, rollback, and inspect database migrations.
This is the primary workflow. Run
makemigrations initto generate themigrations/directory, then usemakemigrations migrateor compile the binary manually as shown below.
Each project has its own compiled migration binary that embeds all registered migrations. The binary is built from Go source files in the migrations/ directory and knows exactly which migrations exist, their dependencies, and their SQL.
your-project/
└── migrations/
├── main.go ← binary entry point (generated by init)
├── go.mod ← module file (generated by init)
├── 0001_initial.go ← migration files (generated by makemigrations)
├── 0002_add_phone.go
└── migrate ← compiled binary (you build this)
makemigrations migrate automatically builds the migrations binary with the correct Go workspace and toolchain settings, then runs it. All arguments are forwarded unchanged.
makemigrations migrate up
makemigrations migrate down --steps 2
makemigrations migrate status
makemigrations migrate showsql
makemigrations migrate fake 0001_initial
makemigrations migrate dagAdd --verbose to see the build step:
makemigrations migrate --verbose upThis is the recommended approach during development because it handles Go workspace conflicts and toolchain version selection automatically. See the Manual Build Guide for how to build the binary yourself.
cd migrations && go mod tidy && go build -o migrate .Run this after:
makemigrations init(first time setup)makemigrations makemigrations(after generating new migrations)
If your project uses a
go.workfile or a non-system Go toolchain, see the Manual Build Guide for the correct environment flags.
Show the migration dependency graph.
./migrations/migrate dag [--format ascii|json]
Flags:
| Flag | Default | Description |
|---|---|---|
--format |
ascii |
Output format: ascii for a text tree, json for machine-readable output |
ASCII output example:
0001_initial
└── 0002_add_phone
└── 0003_add_index
JSON output example:
{
"nodes": ["0001_initial", "0002_add_phone", "0003_add_index"],
"edges": {
"0002_add_phone": ["0001_initial"],
"0003_add_index": ["0002_add_phone"]
},
"leaves": ["0003_add_index"],
"has_branches": false,
"schema_state": { ... }
}The --format json output is used internally by makemigrations makemigrations to reconstruct schema state before diffing.
Apply all pending migrations in topological order.
./migrations/migrate up [--to <migration-name>] [--warn-on-missing-drop]
Flags:
| Flag | Default | Description |
|---|---|---|
--to |
(none) | Stop after applying the named migration |
--warn-on-missing-drop |
false |
Warn and continue when a DROP TABLE, DROP COLUMN, or DROP INDEX fails because the object does not exist |
Examples:
# Apply all pending migrations
./migrations/migrate up
# Apply only up to a specific migration
./migrations/migrate up --to 0003_add_index
# Continue past drop operations that target objects already absent from the database
./migrations/migrate up --warn-on-missing-dropOutput:
Applying 0001_initial... done
Applying 0002_add_phone... done
Applying 0003_add_index... done
If a migration fails, it prints FAILED and returns a non-zero exit code. Migrations already applied are skipped automatically.
Roll back applied migrations in reverse topological order.
./migrations/migrate down [--steps <n>] [--to <migration-name>] [--warn-on-missing-drop]
Flags:
| Flag | Default | Description |
|---|---|---|
--steps |
1 |
Number of migrations to roll back |
--to |
(none) | Roll back until (but not including) this migration name |
--warn-on-missing-drop |
false |
Warn and continue when a DROP TABLE, DROP COLUMN, or DROP INDEX fails because the object does not exist |
Examples:
# Roll back the most recent migration (default)
./migrations/migrate down
# Roll back the last 3 migrations
./migrations/migrate down --steps 3
# Roll back until 0001_initial (rolls back everything after it)
./migrations/migrate down --to 0001_initial
# Continue past drop operations that target objects already absent from the database
./migrations/migrate down --warn-on-missing-dropOutput:
Rolling back 0003_add_index... done
Rolling back 0002_add_phone... done
Show which migrations have been applied and which are pending.
./migrations/migrate status
Output:
Migration Status
------------------------------------------------------------
0001_initial Applied
0002_add_phone Applied
0003_add_index Pending
Run this before and after up/down to verify state.
Print the SQL for all pending migrations without executing it.
./migrations/migrate showsql
Output:
-- 0003_add_index
CREATE INDEX idx_users_phone ON users (phone);
Use this to review what SQL will run before committing to up. Useful for auditing, security reviews, and debugging provider-specific SQL generation.
Already-applied migrations are skipped. The SQL is generated using the current provider (database type configured in main.go).
Mark a migration as applied in the history table without executing its SQL.
./migrations/migrate fake <migration-name>
Arguments:
| Argument | Required | Description |
|---|---|---|
migration-name |
Yes | The exact name of the migration to fake |
Examples:
# Fake a single migration (e.g., for an already-applied initial schema)
./migrations/migrate fake 0001_initial
# Fake all migrations at once using a script loop
for m in 0001_initial 0002_add_phone; do
./migrations/migrate fake "$m"
doneOutput:
Marked "0001_initial" as applied (faked).
When to use fake:
This is primarily used when setting up Go migrations on an existing database. The makemigrations init command prints the exact fake commands needed after it generates 0001_initial.go from a schema snapshot.
Your database already has these tables applied. Mark this migration as applied without re-running SQL:
cd migrations && go mod tidy && go build -o migrate .
./migrate fake 0001_initial
The compiled binary connects to the database using the configuration embedded in migrations/main.go. The file generated by makemigrations init looks like this:
package main
import (
"fmt"
"os"
m "github.com/ocomsoft/makemigrations/migrate"
)
func main() {
app := m.NewApp(m.Config{
DatabaseType: m.EnvOr("DB_TYPE", "postgresql"),
DatabaseURL: m.EnvOr("DATABASE_URL", ""),
})
if err := app.Run(os.Args[1:]); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}| Variable | Default | Description |
|---|---|---|
DATABASE_URL |
"" |
Full DSN (e.g. postgresql://user:pass@host/db) |
DB_TYPE |
postgresql |
Database type: postgresql, mysql, sqlserver, sqlite |
migrate.Config also has DBHost, DBPort, DBUser, DBPassword, DBName, and DBSSLMode fields. They are not wired up in the generated main.go — edit the file to add them when you need per-field connection configuration:
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. See the Manual Build Guide for a full example.
The variable names and defaults are entirely determined by your
migrations/main.go. Customise them freely to match your project's conventions.
The binary stores migration history in a table called makemigrations_history:
CREATE TABLE IF NOT EXISTS makemigrations_history (
id INTEGER PRIMARY KEY,
name VARCHAR(255) NOT NULL UNIQUE,
applied_at TIMESTAMP NOT NULL
);This table is created automatically on first up or status run. It is created using portable SQL compatible with PostgreSQL, MySQL, SQLite, and SQL Server.
# 1. Generate initial migration (if you have an existing schema snapshot)
makemigrations init
# 2. Apply all migrations
makemigrations migrate up
# 3. Verify
makemigrations migrate status# 1. Generate initial migration from existing schema
makemigrations init
# This prints: makemigrations migrate fake 0001_initial
# 2. Mark the initial migration as already applied
makemigrations migrate fake 0001_initial
# 3. Verify
makemigrations migrate status# 1. Edit your YAML schema files
# 2. Generate the migration
makemigrations makemigrations --name "add user preferences"
# Creates: migrations/0004_add_user_preferences.go
# 3. Preview the SQL before applying
makemigrations migrate showsql
# 4. Apply
makemigrations migrate up
# 5. Verify
makemigrations migrate status# Check whether any unapplied migrations exist (fails if pending)
makemigrations makemigrations --check
# Apply migrations as part of deployment
makemigrations migrate up# Review what will be rolled back
makemigrations migrate status
# Roll back the last migration
makemigrations migrate down
# Or roll back multiple steps
makemigrations migrate down --steps 3When two developers create migrations concurrently, the DAG develops branches:
0001_initial
├── 0002_branch_a (developer A)
└── 0003_branch_b (developer B)
The dag command shows this:
./migrate dag0001_initial
├── 0002_branch_a
└── 0003_branch_b
The makemigrations makemigrations command will warn about branches. Resolve with a merge migration:
makemigrations makemigrations --merge
# Creates: migrations/0004_merge_0002_branch_a_and_0003_branch_b.goAfter a merge, the DAG converges to a single leaf again.
Your migration dependencies form a cycle. Check Dependencies fields in your .go migration files.
Database connection failed. Check your environment variables and that the database is reachable:
echo $DATABASE_URL
echo $DB_HOST $DB_USER $DB_NAMEThe binary stops at the first failure and prints FAILED. Migrations already completed in that run are recorded as applied. Fix the failing migration and re-run up.
If someone manually removed a database object that a migration is trying to drop, up or down will fail. Use --warn-on-missing-drop to print a warning and continue instead of stopping:
./migrations/migrate up --warn-on-missing-drop
./migrations/migrate down --warn-on-missing-dropA [WARNING] line is printed for each skipped drop, and the migration is recorded as applied. Only true missing-object errors are skipped — all other errors still stop the migration.
The name passed to --to or fake does not match any registered migration. Use ./migrate dag to list exact names.
- init command — bootstrap the
migrations/directory - makemigrations command — generate
.gomigration files - Architecture — how the Go migration framework works end-to-end