CLI tool
rubenv/sql-migrate avatar
rubenv/sql-migrate

rubenv/sql-migrate: SQL migrations for Go, as a CLI or an embedded library

SQL schema migration tool for Go.

3,417 stars292 forksGoMIT

At a glance

What is it?
sql-migrate applies plain SQL migration files against SQLite, PostgreSQL, MySQL, MSSQL and Oracle, and can be embedded in a Go binary. Its value is that migrations are SQL, not a DSL, and its cost is that rollback depends on you writing the down files.
Who is it for?
Adopt sql-migrate if your team already keeps schema changes as SQL files and you want them applied by a Go binary or an embedded library rather than a separate migration service. Do not adopt it if you need the tool to generate down migrations for you, or if you cannot commit to writing and reviewing the reverse SQL for every up file.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 79 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 25, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What sql-migrate is for, and who ends up using it

The project describes itself as a SQL schema migration tool for Go, based on gorp and goose. That lineage explains the shape of the thing: it is a Go program and a Go library that reads .sql files and applies them in order, recording what has been applied in a bookkeeping table. The audience is Go teams that keep schema changes as SQL rather than as Go structs or a migration DSL. If your migrations are already written by hand as CREATE TABLE and ALTER TABLE statements, sql-migrate runs them without asking you to translate them into another syntax. The README lists the intended capabilities plainly: usable as a CLI tool or as a library, support for SQLite, PostgreSQL, MySQL, MSSQL and Oracle through gorp, migrations embedded into your application, migrations defined with SQL, atomic migrations, up and down migrations to allow rollback, and multiple database types in one project. That last item is the one worth pausing on. A single repository can hold migrations/sqlite3, migrations/postgres and migrations/mysql side by side, each selected by an environment in the config file. Teams that ship the same product against more than one database engine get that separation for free, at the cost of maintaining each dialect's SQL separately.

The mechanism: a config file, a table, and ordered SQL files

Every command needs a configuration file, dbconfig.yml by default, overridable with the -config flag. The file holds one or more named environments, and each environment names a dialect, a datasource, and a directory of migrations. The environment is chosen with -env, which defaults to development. The table setting is optional and defaults to gorp_migrations; that table is where applied migration identifiers and timestamps are recorded. The status command reads it back and prints a table of migration names against either an applied timestamp or the word no. The new command creates an empty template named with the pattern <current time>-<name>.sql, so ordering comes from the timestamp prefix rather than from a hand-maintained sequence number. The up command applies everything outstanding; down applies one migration by default. Both accept -limit to change how many run, and -version to target a specific version, where the version of a file like 1_initial.sql is 1. The README states that -version takes priority over -limit when both are given. There is also -dryrun, which prints migrations instead of applying them. For a library user, the source of migrations is an interface: an in-memory slice, a set of files, bindata, or anything implementing http.FileSystem. That is the part that makes sql-migrate embeddable. The binary and the library share the same engine, so a CLI run and an in-process run against the same directory should apply the same files.

Install and a first migration you can actually run

The README gives two install paths. On older Go versions it uses go get with the ellipsis to pull the package and the command; from Go 1.18 onward it uses go install with @latest. The go.mod in the repository declares go 1.25.0, so the module itself is built with a recent toolchain even though the README states support for go1.13 and later.

bash
go install github.com/rubenv/sql-migrate/...@latest

After that, sql-migrate should be on your PATH. Running it with no arguments prints the command list: down, new, redo, status and up. Next, write a config file. The README's example uses SQLite for development and PostgreSQL for production, which is a useful pattern to copy because it keeps the local loop fast.

yaml
development:
  dialect: sqlite3
  datasource: test.db
  dir: migrations/sqlite3

production:
  dialect: postgres
  datasource: dbname=myapp sslmode=disable
  dir: migrations/postgres
  table: migrations

Create the first migration with the new command, which produces an empty file named with the current time and the name you pass. Fill in the up and down sections, then check what the tool thinks the state is before applying anything.

bash
sql-migrate new create-users
sql-migrate status
sql-migrate up

The status output is a two-column table listing each migration file and either an applied timestamp or no. If the file you just created shows as not applied, up will run it. One caveat the README calls out explicitly: on MySQL you must append ?parseTime=true to the datasource string, because the driver needs it to scan the timestamp column in the bookkeeping table. Without it, status and up can fail on the applied-time read.

Rollback is only as good as the down file you wrote

The feature list says up and down migrations allow rollback, and the down command exists. What the README does not document is any generation of the down half. The new command creates an empty template, and the migration format carries both directions, but nothing in the project's own documentation suggests the tool derives one from the other. In practice that means a down migration is a second piece of SQL you write by hand and review by hand, and it can silently be wrong. A CREATE TABLE with a matching DROP TABLE is easy. A column type change, a data backfill, or a dropped column with data in it is not reversible in any mechanical sense, and the tool will run whatever you wrote regardless. This is the main failure mode to plan for: teams treat the existence of a down command as a safety net, then discover during an incident that the down file was a stub. The second constraint is atomicity. The README lists atomic migrations, but atomicity is bounded by what the underlying database and driver support for DDL. The repository also does not document a locking scheme for concurrent runners, so two processes pointed at the same database and the same migration directory are not something the README addresses. If you deploy multiple replicas that each run migrations at startup, that is a design decision you are making without documented guidance from the project.

Where sql-migrate is the wrong tool

It is the wrong tool when you want migration files generated from your Go model definitions. sql-migrate applies SQL you wrote; it does not diff your structs against the live schema and emit the ALTER statements. Projects that expect that workflow will find the new command produces an empty file and nothing more. It is also the wrong tool when the schema changes are owned by a language runtime other than Go and the team does not want a Go binary in the deployment path. The CLI is a Go program; the library is a Go import. There is no documented non-Go client. A third case: if your database is not one of SQLite, PostgreSQL, MySQL, MSSQL or Oracle, the gorp layer is the boundary, and the README does not describe an extension point for adding a dialect. Finally, the Oracle support deserves scrutiny before you commit. The README documents two Oracle drivers, oci8 and godror, and states that neither is pure Go and both rely on the Oracle Instant Client. Installation requires a build tag, either -tags oracle or -tags godror, plus a shared library on LD_LIBRARY_PATH. That is a materially heavier build than the default path and it changes how you produce your binary.

Compared with golang-migrate

The obvious alternative in the same language is golang-migrate, which appears in the search data around this project. The difference in approach is mostly about what the tool owns. golang-migrate is a standalone CLI with its own driver set and its own set of sources for migration files, and it is designed to be driven as a separate binary or imported as a library in Go. sql-migrate's distinguishing move is the gorp connection and the environment-per-dialect config file: one dbconfig.yml can describe development on SQLite and production on PostgreSQL, and the same repository can hold migrations for both. If your project already supports several database engines and you want that reflected in one config file rather than in separate invocations, sql-migrate's structure matches that shape. If you want a tool that is not tied to gorp and has its own broader set of file sources, golang-migrate is the one to look at. Neither generates down migrations for you, so that trade-off is not a differentiator between them.

Maintenance, licence and the cost of staying current

The repository is not archived, and the last push was on 2026-07-14. The module declares go 1.25.0 in go.mod while the README still states support for go1.13 and later, so the README's compatibility claim lags the module's own toolchain requirement. That gap is worth checking before you assume an old Go version will build it. The dependency list is broad for a migration tool: drivers for MSSQL, MySQL, Oracle, PostgreSQL and SQLite, plus gorp, mitchellh/cli, tablewriter and yaml.v2, with a second block of indirect dependencies that includes Masterminds/sprig and golang.org/x/crypto. Each of those is a thing you inherit and eventually have to update. The Dockerfile pins GO_VERSION to 1.20.6 and ALPINE_VERSION to 3.12 by default, both overridable as build args, and builds the CLI with CGO_ENABLED=0. That is fine for the pure-Go dialects, but the README's Oracle instructions require a non-Go shared library, so an Oracle build cannot follow the same static path. The licence is MIT, which is permissive and imposes no copyleft obligation on your own code; the usual caveat applies that the bundled dependencies carry their own licences, and this is not legal advice. Upgrading is a matter of bumping the module version and rebuilding, since the CLI is a single binary and the library is an import. The real upgrade cost is not the tool, it is the migration files themselves, which no version bump will validate for you.

Editorial conclusion

Adopt sql-migrate if your team already keeps schema changes as SQL files and you want them applied by a Go binary or an embedded library rather than a separate migration service. Do not adopt it if you need the tool to generate down migrations for you, or if you cannot commit to writing and reviewing the reverse SQL for every up file. Before rolling it out, verify two things against your own database: that the dialect name in dbconfig.yml matches the driver you plan to import, and that a down migration actually reverses its up migration on a copy of your schema.

Frequently asked questions

How do I install sql-migrate?

The README gives two commands: go get -v github.com/rubenv/sql-migrate/... for older Go versions, and go install github.com/rubenv/sql-migrate/...@latest from Go 1.18 onward. Oracle support needs a build tag such as -tags godror and the Oracle Instant Client installed.

What are SQL migrations in the context of sql-migrate?

They are plain SQL files applied in order and recorded in a bookkeeping table, which defaults to gorp_migrations. Each migration carries an up direction and a down direction, and the new command creates an empty template named with the pattern <current time>-<name>.sql.

How do I migrate a database with sql-migrate?

Write a dbconfig.yml with a dialect, a datasource and a migration directory, then run sql-migrate up to apply everything outstanding. Use sql-migrate status to see which files are applied, and -env to pick an environment other than the default development.

Does sql-migrate work with MySQL?

Yes, with one documented caveat: the datasource string must include ?parseTime=true, for example root@/dbname?parseTime=true. The README links to the go-sql-driver/mysql documentation for the reason.

Can sql-migrate be embedded in a Go application instead of run as a CLI?

Yes. The README shows importing github.com/rubenv/sql-migrate and supplying migrations from an in-memory slice, a set of files, bindata, or anything implementing http.FileSystem.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. rubenv/sql-migrate on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/rubenv-sql-migrate.svg)](https://hysenlabs.com/projects/rubenv-sql-migrate)