A simple, robust, and platform-agnostic migration tool for PostgreSQL.
Works great for local development, CI/CD, and Docker environments.
Follow this flow to install and run your first migration in 2 minutes.
Option 1: Using Make (Recommended)
git clone github.com/mystaline/mig
cd migration-tool
make installOption 2: Manual Installation
git clone github.com/mystaline/mig
cd migration-tool
go build -o mig ./cmd/main.go
sudo mv mig /usr/local/bin/You don't need a config file. Just tell mig where your files are and how to connect to the DB.
The easiest way is to export variables in your terminal so you don't have to type them every time:
# RECOMENDED: Run this in your terminal before starting
export MIGRATIONS_DIR=./migrations # Or wherever your .sql files are
export DB_URL="postgres://user:pass@localhost:5432/mydb?sslmode=disable"Step A: Create a Migration
This creates a pair of .up.sql and .down.sql files.
mig create add_users_tableStep B: Write SQL
- Edit
..._add_users_table.up.sql: Write yourCREATE TABLEstatement. - Edit
..._add_users_table.down.sql: Write yourDROP TABLEstatement.
Step C: Ensure Integrity (Recommended)
Ensure your Up and Down logic is perfect by running the integrity test.
mig testStep D: Apply Changes
Run the pending migrations against your DB.
mig up| Command | Description |
|---|---|
mig init |
Initialize the schema_migrations tracking table in your DB. |
mig create <name> |
Generate timestamped .up.sql and .down.sql files. |
mig up |
Apply all pending migrations. |
mig down |
Rollback the last migration only. |
mig status |
Check which migrations are applied vs pending. |
mig test |
Run a safety check (Up -> Down -> Up) on a temporary DB. |
You can configure mig in 3 ways (in order of precedence):
- CLI Flags: Pass arguments directly to the command.
Required for up, down, status, test, init.
- Flag:
--db-url "postgres://..." - Env Var:
DB_URL
Optional. Defaults to ./migrations if not set.
- Flag:
--dir ./path/to/files - Env Var:
MIGRATIONS_DIR
For example:
mig create add_balance_column --dir pkg/migrations/wallet
mig up --dir pkg/migrations/wallet --db-url "postgres://admin:secret@localhost:5432/wallet_db"- Environment Variables:
export VAR=...in your shell.
Export variables in your shell before running the command. For example:
export DB_URL="postgres://admin:secret@localhost:5432/wallet_db"
export MIGRATIONS_DIR=pkg/migrations/wallet.envFile: A file named.envin the current directory.
If you don't have a .env file, you can create one in the root of your project then define the variables in it. Variables name should be in uppercase and separated by underscores. For example:
DB_URL="postgres://admin:secret@localhost:5432/wallet_db"
MIGRATIONS_DIR=pkg/migrations/walletThe tool tracks migrations in a simple table called schema_migrations.
| Column | Type | Description |
|---|---|---|
version |
VARCHAR | Unique ID (Timestamp) of the migration. (e.g., 20260131120000) |
dirty |
BOOLEAN | Safety Lock. true while a migration is running or if it failed. |
applied_at |
TIMESTAMP | When the migration successfully completed. |
- Start: Tool creates a row with
version=...anddirty=true. - Execute: Tool runs your
.up.sqlscript within a transaction. - Success: Tool updates the row to
dirty=false. - Failure: Tool exits. The row remains
dirty=true.- Note: Because of Transactional DDL, your SQL changes are rolled back, but this dirty record remains to force you to review the error.
If a migration fails (e.g., syntax error in SQL), mig status will show it as Pending or Dirty, and mig up will refuse to run until you fix it.
Step-by-Step Fix:
-
Identify the Error: Read the error log from the failed
mig upcommand. -
Fix Code: Open your
...up.sqlfile and correct the SQL syntax. -
Unlock (Clean Dirty State): Since the SQL transaction rolled back, your data is safe, but the "lock" is still on. You must manually delete the dirty record:
# Connect to your DB psql "$DB_URL" # Run this query (replace VERSION with your failed timestamp) DELETE FROM schema_migrations WHERE version = '20260131172602';
-
Retry:
mig up
You don't need Go installed to run this tool. You can use the Docker image to run commands against your database.
The migration tool runs inside a container, but your SQL files live on your computer.
We use a volume (-v $(pwd)/migrations:/migrations) to give the container access to your local folder.
This way, the tool can read your latest SQL files without you needing to rebuild the Docker image every time you make a change.
You can run any mig command by appending it to the end of the docker line.
Run mig status:
docker run --rm \
-e DB_URL="postgres://user:pass@host.docker.internal:5432/mydb" \
-v $(pwd)/migrations:/migrations \
mystaline/migration-tool statusRun mig up:
docker run --rm \
-e DB_URL="postgres://user:pass@host.docker.internal:5432/mydb" \
-v $(pwd)/migrations:/migrations \
mystaline/migration-tool up> Note: host.docker.internal allows the container to connect to a Postgres database running on your local machine outside of Docker.
Add the migrator to your stack to automatically run migrations or simplify commands.
services:
migrator:
image: mystaline/migration-tool:latest
environment:
- DB_URL=postgres://user:pass@db:5432/mydb
# Tell the tool where to find the mounted files
- MIGRATIONS_DIR=/my-project/migrations
volumes:
# Map local folder -> container folder
- ./my-project/migrations:/migrations
depends_on:
db:
condition: service_healthy
# Optional: Automatically run 'up' when the container starts
command: upRunning commands via Compose:
Once defined in your compose file, you can run commands easily:
# Check status
docker compose run --rm migrator status
# Rollback one step
docker compose run --rm migrator downQ: Where should I put my migration files?
A: Anywhere you want! Just point the tool to that folder using --dir or MIGRATIONS_DIR. We recommend keeping them nicely organized in your project repo, e.g., internal/db/migrations.
Q: What if a migration fails?
A: mig takes a safety-first approach:
- Transactional Rollback: Your SQL changes are automatically rolled back, so your database schema stays clean.
- Dirty State: The version is marked as dirty in the
schema_migrationstable to lock the system. - Manual Fix: You must fix your
.sqlfile and then manually remove the dirty record fromschema_migrations.
Q: Why do I need .down.sql files?
A: They allow you to undo changes safely (mig down). But crucially, mig test uses them to prove your migration is reversible and safe before you even deploy it.