A simple, powerful roll-forward database migration tool for PostgreSQL written in Go. Inspired by graphile-migrate.
- Development-friendly: Edit and apply your SQL migrations continuously during development
- Idempotent migrations: Migrations are designed to be reapplied without errors
- Version tracking: Migrations are tracked in the database with hash-based integrity
- Simple CLI: Easy to use commands for all migration operations
go install github.com/techtonic-org/rf-migrate@latestVisit the releases page to download the pre-compiled binary for your platform.
Go 1.24 introduces the new tool directive that simplifies managing tool dependencies:
# Add rf-migrate as a tool dependency
go get -tool github.com/techtonic-org/rf-migrate@latest
# Use it directly with go tool
go tool rf-migrate migrateThis will add a tool directive to your go.mod file:
tool github.com/techtonic-org/rf-migrate v1.2.3
You can now run rf-migrate directly using go tool rf-migrate in your Makefile:
# Default database connection URL and migrations directory
DB_URL ?= postgres://postgres:postgres@localhost:5432/myapp?sslmode=disable
MIGRATIONS_DIR ?= ./db/migrations
migrate:
@echo "Applying migrations..."
@DATABASE_URL=$(DB_URL) RF_MIGRATION_DIR=$(MIGRATIONS_DIR) go tool rf-migrate migrate
migrate-watch:
@echo "Watching migrations..."
@DATABASE_URL=$(DB_URL) RF_MIGRATION_DIR=$(MIGRATIONS_DIR) go tool rf-migrate watch
migrate-commit:
@if [ -z "$(name)" ]; then echo "Error: migration name required. Use 'make migrate-commit name=migration_name'"; exit 1; fi
@echo "Committing migration: $(name)"
@DATABASE_URL=$(DB_URL) RF_MIGRATION_DIR=$(MIGRATIONS_DIR) go tool rf-migrate commit --name "$(name)"For older Go versions, use the tools.go approach:
- Create a
tools/tools.gofile:
//go:build tools
// +build tools
package tools
import (
_ "github.com/techtonic-org/rf-migrate" // Import for go mod dependency
)- Add to your Makefile:
# Default database connection URL and migrations directory
DB_URL ?= postgres://postgres:postgres@localhost:5432/myapp?sslmode=disable
MIGRATIONS_DIR ?= ./db/migrations
migrate:
@echo "Applying migrations..."
@DATABASE_URL=$(DB_URL) RF_MIGRATION_DIR=$(MIGRATIONS_DIR) go run github.com/techtonic-org/rf-migrate migrate
migrate-watch:
@echo "Watching migrations..."
@DATABASE_URL=$(DB_URL) RF_MIGRATION_DIR=$(MIGRATIONS_DIR) go run github.com/techtonic-org/rf-migrate watch
migrate-commit:
@if [ -z "$(name)" ]; then echo "Error: migration name required. Use 'make migrate-commit name=migration_name'"; exit 1; fi
@echo "Committing migration: $(name)"
@DATABASE_URL=$(DB_URL) RF_MIGRATION_DIR=$(MIGRATIONS_DIR) go run github.com/techtonic-org/rf-migrate commit --name "$(name)"This project uses go-semantic-release to automatically determine the version number and publish releases. New versions are automatically released when changes are pushed to the main branch.
To trigger specific types of releases, use the following commit message format:
feat: add new feature- Triggers a MINOR version bump (e.g., 1.1.0 → 1.2.0)fix: fix a bug- Triggers a PATCH version bump (e.g., 1.1.0 → 1.1.1)feat!: add feature with breaking change- Triggers a MAJOR version bump (e.g., 1.1.0 → 2.0.0)
Other commit types (docs, style, refactor, perf, test, chore) won't trigger a new release.
Run make help to see a full list of supported commit types and formats.
RF-Migrate can be configured in multiple ways (in order of precedence):
-
Command-line flags (highest precedence):
rf-migrate --database-url "postgres://postgres:postgres@localhost:5432/mydb?sslmode=disable" --migration-dir "./migrations"
-
Environment variables:
export DATABASE_URL="postgres://postgres:postgres@localhost:5432/mydb?sslmode=disable" export RF_MIGRATION_DIR="./migrations" rf-migrate migrate
-
Configuration file (YAML or JSON):
# rfmigrate.yaml example databaseUrl: "postgres://postgres:postgres@localhost:5432/mydb?sslmode=disable" migrationDir: "./migrations"
The config file location can be specified with
-cor--configflag:rf-migrate -c custom-config.yaml migrate
If not specified, it defaults to
./rfmigrate.yamlor./rfmigrate.json.
The tool will automatically create the migration directory and an empty current.sql file if they don't exist.
However, you can also manually set up your migration directory structure:
mkdir -p migrations
touch migrations/current.sql-
Edit your
current.sqlfile with your database changes -
Watch mode during development:
rf-migrate watch
This continuously applies changes from
current.sqlto your database -
Commit your changes when satisfied:
rf-migrate commit --name "add_users_table"This creates a timestamped migration file like
20231010123045_add_users_table.sqlin the same directory ascurrent.sql -
Uncommit if needed:
rf-migrate uncommit
This restores the last migration to
current.sql
Apply all migrations:
rf-migrate migrateThis applies all timestamped migration files in the specified migration directory that haven't been applied yet.
Migrations should be idempotent, typically using IF EXISTS and IF NOT EXISTS clauses:
-- Drop if exists
drop table if exists users;
-- Create new
create table if not exists users (
id serial primary key,
username varchar(255) not null unique,
created_at timestamp not null default now()
);MIT