Database Migrations in Go (Golang): How to Manage Data Schema
When developing applications in Go, sooner or later you need to change the database structure: add new tables, modify existing columns, create indexes. Manually executing SQL scripts across different environments (local development, staging, production) leads to errors and desynchronization. The solution is database migrations.
The Go ecosystem has several mature libraries for managing migrations. In this article, we will thoroughly examine three of the most popular tools: golang-migrate/migrate, pressly/goose, and rubenv/sql-migrate. You will learn how to install, configure, and use each one, as well as get acquainted with best practices.
What are migrations and why are they needed?
A migration is a file (or a set of files) that contains SQL commands for changing the database schema. Each migration has a unique identifier (usually a timestamp or number) and two directions: up (applying changes) and down (reverting changes).
Main advantages of using migrations:
- Schema versioning — the change history is stored in the repository alongside the code.
- Reproducibility — identical changes across all environments.
- Rollback — the ability to quickly revert to a previous schema version.
- Team collaboration — developers do not overwrite each other's changes.
1. golang-migrate/migrate
The most popular library for migrations in Go. Supports PostgreSQL, MySQL, SQLite, MongoDB, and many other databases.
Installation
go get -u github.com/golang-migrate/migrate/v4go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latestMigration Structure
Create a migrations folder and add files with a prefix in the format {timestamp}_{title}.{direction}.sql:
migrations/ 000001_create_users_table.up.sql 000001_create_users_table.down.sql 000002_add_email_column.up.sql 000002_add_email_column.down.sqlMigration Example (up)
-- 000001_create_users_table.up.sqlCREATE TABLE users ( id SERIAL PRIMARY KEY, name VARCHAR(100) NOT NULL, created_at TIMESTAMP DEFAULT NOW());Migration Example (down)
-- 000001_create_users_table.down.sqlDROP TABLE IF EXISTS users;Applying Migrations from Go Code
package main
import ( "github.com/golang-migrate/migrate/v4" _ "github.com/golang-migrate/migrate/v4/database/postgres" _ "github.com/golang-migrate/migrate/v4/source/file")
func main() { m, err := migrate.New( "file://migrations", "postgres://localhost:5432/mydb?sslmode=disable") if err != nil { panic(err) } if err := m.Up(); err != nil && err != migrate.ErrNoChange { panic(err) }}2. pressly/goose
Goose is a lightweight and fast tool that also supports writing migrations in Go (not just SQL). It is ideal for projects that require code-side logic.
Installation
go get github.com/pressly/goose/v3/cmd/gooseCreating a Migration
goose create create_users_table sqlThis will create a file like 20250101120000_create_users_table.sql. Contents:
-- +goose UpCREATE TABLE users ( id SERIAL PRIMARY KEY, name VARCHAR(100) NOT NULL);
-- +goose DownDROP TABLE IF EXISTS users;Go Migrations (Advanced Option)
Goose allows you to write migrat