pressly/goose: SQL and Go Database Migrations from One Binary
A database migration tool. Supports SQL migrations and Go functions.
At a glance
- What is it?
- Goose applies versioned schema changes to Postgres, MySQL, SQLite and other databases through a CLI or a Go library, with migrations written either as SQL files or as Go functions. The trade-off is a small versioning table and a command set you have to operate yourself.
- Who is it for?
- Adopt pressly/goose if your schema lives in a Go service or you want plain SQL migrations applied by a single binary, and you are prepared to run up, status and down yourself. Do not adopt it if you need a migration runner that also provisions databases, manages rollback state automatically, or gives you a hosted dashboard; goose tracks versions in a table and leaves the rest to you.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 1 day 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 29, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem goose solves: schema changes that have to be replayed in order
A database schema drifts the moment more than one environment exists. Development gets a column, staging does not, and production is somewhere else. Goose addresses that by treating each schema change as a numbered file and recording which numbers have already run. The README describes it as "a database migration tool. Both a CLI and a library," and that dual identity is the point: the same migration files can be applied by a shell command in CI or by an embedded Go program at startup.
The audience is narrow and specific. If your application is written in Go, or your team is comfortable shipping .sql files alongside a service, goose fits without ceremony. It is not aimed at teams who want a graphical migration console or who expect the tool to infer rollbacks. Every migration is something you write, and the tool's job is ordering, tracking and applying.
How goose tracks state: the goose_db_version table and the up/down pairs
Goose keeps its bookkeeping in a table. The help output shows a -table flag with the default value "goose_db_version", and notes that if you use a schema that is not public you should set schemaname.goose_db_version when running commands. That single table is the entire state model. A migration is applied, a row is written, and status reads those rows back.
Migrations come in two forms. SQL migrations are files created by goose create NAME sql, and the README shows the generated name carrying a timestamp, for example 20170506082420_add_some_column.sql. Go migrations are created with goose create NAME go and are described in the README as being invoked with your own goose binary, which is a real constraint: a Go migration is a function compiled into a program, not something the stock binary can execute for you. The README lists supporting Go migrations written as plain functions and embedded migrations among the features.
Ordering is by version, and goose also supports out-of-order migrations. The -allow-missing flag is documented as applying missing (out-of-order) migrations, and goose fix exists to apply sequential ordering to migration files. Those two mechanisms are the pressure valve for the classic problem of two branches merging with overlapping timestamps.
Installing goose and running a first migration
The README gives the install command directly. It places the goose binary in $GOPATH/bin.
go install github.com/pressly/goose/v3/cmd/goose@latestmacOS users have a second route, since the README notes goose is available as a Homebrew formula:
brew install gooseIf the resulting binary is too large, the README documents build tags that exclude drivers you do not need, for example no_postgres, no_mysql, no_sqlite3 and no_ydb.
A first migration against SQLite is three commands. Create the file, check status, then apply it.
goose sqlite3 ./foo.db create init sql
goose sqlite3 ./foo.db status
goose sqlite3 ./foo.db upThe create command prints the new filename, which you then edit to contain the actual DDL. The status command dumps the migration status for the current database, and up migrates the database to the most recent version available. The README's own example output for up shows lines like OK 001_basics.sql and OK 002_next.sql, so a successful run is visibly a list of applied files.
The same commands work against a server database by swapping the driver and connection string. The help output gives the shape for Postgres:
goose postgres "user=postgres dbname=postgres sslmode=disable" statusAlternatively you can set GOOSE_DRIVER, GOOSE_DBSTRING and GOOSE_MIGRATION_DIR as environment variables and drop the driver and DBSTRING arguments entirely.
Where goose stops: rollback discipline and Go migration binaries
The command set includes down, down-to, redo and reset, so rollback is available. What the README does not document is automatic generation of the reverse of a migration. You write the down section yourself, and if you write it wrong, goose will run it anyway. That is the most common way a goose setup becomes dangerous: the up path is tested constantly and the down path almost never is, until the day it is needed in production.
Go migrations carry a second constraint. The README's create example produces 20170506082421_fetch_user_data.go and states that you invoke it with your own goose binary. The stock goose binary installed from the module path does not know about your functions. If your team wants data backfills or transformations written in Go rather than SQL, the deployment story changes: you now build and ship a binary, not just a migration directory. Teams that want migrations to stay declarative should stay with SQL files.
The -no-versioning flag is another boundary worth understanding before you use it. The help text describes it as applying migration commands with no versioning, in file order, from the directory pointed to. That is a different tool wearing the same name: no version table, no status, no down.
goose against golang-migrate: two different bets on state
The closest comparison in the Go ecosystem is golang-migrate, and the difference is mostly in how much the tool decides for you. Golang-migrate is built around paired up and down files with a strict naming convention and its own CLI, and it is oriented toward being the single tool that owns the migration lifecycle. Goose instead exposes a smaller command surface and a library that you can call from application code, which is why the README emphasizes embedded migrations and Go functions.
Concretely: goose gives you a -table flag to rename its bookkeeping table and a schemaname.goose_db_version convention for non-public schemas, and it lets you opt into out-of-order application with -allow-missing. If your workflow involves long-lived feature branches that each add migrations, that flag is the difference between a blocked deploy and a merged one. If your workflow requires the migration tool to also verify checksums of already-applied files, goose's documented command set does not present that as a feature, and you should look at tools built around it.
Licence, maintenance and the cost of upgrading
The repository's licence is reported as NOASSERTION, which means the automated classification could not match the LICENSE file to a known identifier. The LICENSE file exists at the top level, so the terms are there to read, but nothing in the repository metadata states which licence it is. Treat the licence as something to confirm from the file itself before you depend on goose in a distributed product, and get a real answer from whoever handles licensing on your team rather than from a tool's guess.
Maintenance is easier to judge. The repository is not archived, and the last push was on 2026-09-12. Recent releases include v3.28.0 on 2026-09-02, v3.27.3 on 2026-07-22 and v3.27.2 on 2026-06-30, so the release cadence over the past few months is steady. The go.mod declares go 1.26.0, which sets a floor on the toolchain you need to build it from source.
Upgrade cost is mostly the Go toolchain and the driver set. Because goose vendors drivers for ClickHouse, MySQL, pgx, MSSQL, Vertica, YDB, libsql and modernc sqlite, a go get of the module pulls a wide dependency graph. The build tags exist precisely to cut that down. The CHANGELOG.md at the repository root is where the actual breaking-change record lives; the README does not carry a migration guide for major versions.
Editorial conclusion
Adopt pressly/goose if your schema lives in a Go service or you want plain SQL migrations applied by a single binary, and you are prepared to run up, status and down yourself. Do not adopt it if you need a migration runner that also provisions databases, manages rollback state automatically, or gives you a hosted dashboard; goose tracks versions in a table and leaves the rest to you. Verify first that the driver you need is compiled into your binary, since the lite build tags exclude drivers, and check which goose_db_version table name and schema your commands will write to.
Frequently asked questions
How do I install the goose CLI?
The README gives go install github.com/pressly/goose/v3/cmd/goose@latest, which puts the goose binary in your $GOPATH/bin directory. On macOS the README also notes goose is available as a Homebrew formula, installed with brew install goose.
What does the goose status command show?
The help output describes status as dumping the migration status for the current database. It reads the goose_db_version table, whose name can be changed with the -table flag.
Does goose support databases other than Postgres?
The README lists Postgres, MySQL, MariaDB, Spanner, SQLite, YDB, ClickHouse, MSSQL, Vertica and more. The help output enumerates drivers including postgres, mysql, sqlite3, spanner, mssql, azuresql, redshift, tidb, clickhouse, ydb, starrocks and turso.
Can I write a migration as a Go function instead of SQL?
Yes. goose create NAME go produces a Go migration file, and the README states you invoke it with your own goose binary. That means the stock binary cannot run it; the function has to be compiled into a program you build.
What happens if two migrations have out-of-order timestamps?
The -allow-missing flag applies missing (out-of-order) migrations, and goose fix applies sequential ordering to migration files. The -s flag makes new migrations use sequential numbering instead of timestamps.
Official sources
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.
[](https://hysenlabs.com/projects/pressly-goose)