Self-hosted service
amacneil/dbmate avatar
amacneil/dbmate

Dbmate: a single-binary SQL migration tool for polyglot stacks

🚀 A lightweight, framework-agnostic database migration tool.

7,418 stars381 forksGoMIT

At a glance

What is it?
Dbmate runs plain SQL migrations against MySQL, MariaDB, PostgreSQL, SQLite and ClickHouse from one standalone binary. It is a good fit when several services in different languages need the same migration workflow, and a poor fit when you want schema changes generated from model code.
Who is it for?
Adopt dbmate if your migrations are hand-written SQL and your services are written in more than one language, because the same binary and the same DATABASE_URL convention work everywhere. Skip it if you expect the tool to generate schema changes from application models, or if you need per-migration control over transaction behaviour that the README does not document.
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 7 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 29, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem dbmate solves: one migration workflow across unrelated runtimes

Most migration tools live inside the framework they serve. Rails has its own, Django has its own, and each carries assumptions about the language, the ORM and the project layout. That is fine until one team runs a Go service, a Node.js API and a Python worker against the same PostgreSQL instance. Now the schema has three migration histories written in three idioms, and nobody can tell from a single command what state the database is in.

Dbmate takes the opposite position. It is a standalone command line tool, and the README states it can be used with Go, Node.js, Python, Ruby, PHP, Rust, C++, or any other language. It does not read your application code, does not import your models, and does not care which framework produced the service. The only shared artifact is a directory of timestamped SQL files and a schema_migrations table inside the database.

The intended audience is teams that already write SQL by hand and want the migration runner to stay out of the way. If you would rather have migrations derived from struct tags or model classes, dbmate is the wrong shape entirely. Its README is explicit that migrations are plain SQL, and the feature list ends with a line that reads less like marketing than a positioning statement: it does not try to upsell you on a SaaS service.

How dbmate executes a migration: files, timestamps and a schema_migrations table

The mechanism is small enough to describe in full. Migration files live in a directory, ./db/migrations by default, and each filename begins with a timestamp version. The README notes that timestamp versioning avoids version number conflicts when several developers create migrations in parallel, which is the practical reason it beats a sequential integer counter on a busy branch.

When you run dbmate migrate, the tool connects using the URL from the DATABASE_URL environment variable, reads the applied versions from a table named schema_migrations by default, and applies whatever files are pending. The README states that migrations run atomically inside a transaction. Each migration file carries both an up and a down section, which is what makes dbmate rollback able to reverse the most recent migration.

Two pieces of state sit outside the database. The first is the .env file, which dbmate reads natively so configuration can be shared between team members without exporting variables by hand. The second is schema.sql, which dbmate writes on migrate and rollback unless you pass --no-dump-schema. Keeping a schema dump in the repository means schema changes show up as a readable diff in code review, independent of the migration files themselves. The dump is produced by the underlying client tools, and the README shows that dbmate dump -- can forward extra arguments directly to mysqldump or pg_dump.

Atomicity has a boundary worth naming. The README claims transaction-wrapped migrations, but the per-database documentation for statements that cannot run inside a transaction, such as certain DDL on MySQL, is not spelled out in the README. Treat that as something to verify against your own engine before you rely on it.

Installing dbmate and running the first migration

Dbmate ships as a single self-contained binary, and the README lists five installation routes. On macOS, Homebrew is the shortest path. The binary prints its usage help immediately after installation, which is the quickest way to confirm the install worked.

bash
brew install dbmate
dbmate --help

On Linux the README downloads the release binary straight into /usr/local/bin and marks it executable.

bash
sudo curl -fsSL -o /usr/local/bin/dbmate https://github.com/amacneil/dbmate/releases/latest/download/dbmate-linux-amd64
sudo chmod +x /usr/local/bin/dbmate
/usr/local/bin/dbmate --help

If your project is already managed by npm, the README installs dbmate as a development dependency and invokes it through npx, which keeps the version pinned per project.

bash
npm install --save-dev dbmate
npx dbmate --help

Docker images are published to GitHub Container Registry as ghcr.io/amacneil/dbmate. The README warns that you need --network=host, or one of the workarounds linked in the project issue tracker, for the container to reach a database on the host. Creating a migration inside the container also needs a bind mount so the container can see your working directory.

bash
docker run --rm -it --network=host ghcr.io/amacneil/dbmate --help
docker run --rm -it --network=host -v "$(pwd)/db:/db" ghcr.io/amacneil/dbmate new create_users_table

With the binary in place, point it at a database and generate a migration. The new command creates a timestamped file in the migrations directory. Running up creates the database if it does not exist and applies every pending migration, which is convenient on a fresh development machine and is also the command most people put in a deployment script.

bash
export DATABASE_URL="postgres://postgres:postgres@localhost:5432/myapp?sslmode=disable"
dbmate new create_users_table
dbmate up

After up completes, dbmate writes schema.sql unless you passed --no-dump-schema. You should see the new table in that file, and dbmate status should list the migration as applied. The status command supports --exit-code and --quiet, which is what you want in a CI check that should fail when a migration is pending.

Where dbmate is the wrong tool

The first limitation is the one that defines the project: there is no model-to-schema generation. Every column, index and constraint is typed by hand. Teams accustomed to declaring a field once and letting the framework emit the DDL will find this a step backwards, and for a single-language application with a mature built-in migrator, dbmate adds a second tool without removing the first.

The second is operational. The README documents --strict, which fails if migrations would be applied out of order, and the --wait flag with --wait-timeout and --wait-interval for waiting on a database that is still starting. What the README does not document is rollback of a failed migration that was already partially applied, or any repair command for a migrations table that has drifted from the files on disk. If a migration fails halfway and the transaction does not cover the statement, the recovery path is manual SQL, not a dbmate command.

The third is scale of change. Dbmate applies migrations one at a time and dumps the schema after each run. For a very large schema with hundreds of migrations, the dump step runs on every migrate and rollback, and there is no documented incremental mode beyond turning it off with --no-dump-schema. That flag exists precisely because the dump is not free.

Finally, dbmate is a runner, not a planner. It will not warn you that dropping a column breaks a running service, and it will not coordinate a migration across several database instances. It applies SQL to one URL.

Dbmate compared with goose, Alembic and Flyway

The related searches around dbmate cluster on a handful of comparisons, and the differences are mostly about where the migration logic lives.

Goose, a Go migration tool, is the closest in spirit: it also runs SQL migrations and also ships as a Go binary. The practical difference is that goose is embedded in Go projects as a library and its CLI is a convenience around that library. Dbmate is the reverse. The README does document a library mode, including embedding migrations, but the primary interface is the standalone binary, which is why it works unchanged from a Python or Node.js service. If your whole stack is Go and you want migrations invoked from application startup, goose fits more naturally.

Alembic is the Python answer, and it works from SQLAlchemy metadata. That means migrations can be autogenerated from model changes, which dbmate does not attempt. The trade is that Alembic expects a Python environment and a SQLAlchemy model layer; dbmate expects neither.

Flyway is the JVM equivalent, and it is the closest in workflow: versioned SQL migrations, a history table, a command line runner. Flyway's wider footprint comes with a JVM runtime and a larger feature surface, including documented repair and baseline commands. Dbmate's advantage is distribution: one static binary, no runtime, and the README's Linux install is a single curl into /usr/local/bin.

The honest summary is that dbmate competes on portability and on staying out of your language's way. It does not compete on autogeneration, and it does not compete on recovery tooling.

Maintenance, upgrades and what the MIT licence means here

The repository is not archived, and the last push was on 2026-09-19, the same day as the v2.36.0 release. Both facts come from the repository metadata. Release cadence in the recent window is roughly one release every three to six weeks, with v2.35.0 on 2026-08-07, v2.35.1 on 2026-08-26 and v2.36.0 on 2026-09-19. That is a steady cadence, though it says nothing about how quickly a specific bug will be fixed.

The upgrade cost is low by construction. Dbmate is a binary you replace, not a library you compile against, unless you use the library mode documented in the README. The migration files themselves are plain SQL and do not depend on the dbmate version, so upgrading the binary does not require rewriting migrations. The schema_migrations table is the only persistent state dbmate owns, and the README exposes its name through --migrations-table and DBMATE_MIGRATIONS_TABLE if you need to point at a different table.

Dbmate is MIT licensed. In practical terms that permits commercial and closed-source use, modification and redistribution provided the copyright notice and licence text are preserved. The repository contains a LICENSE file at the top level. This is a description of the licence text, not legal advice; if your organisation has a policy on bundled third-party binaries, run the licence through your own review. The dependency list in go.mod is long because of the BigQuery and Spanner drivers, which is worth knowing if your legal team reviews transitive licences rather than just the top-level one.

Editorial conclusion

Adopt dbmate if your migrations are hand-written SQL and your services are written in more than one language, because the same binary and the same DATABASE_URL convention work everywhere. Skip it if you expect the tool to generate schema changes from application models, or if you need per-migration control over transaction behaviour that the README does not document. Before rolling it out, run dbmate status --exit-code in CI against a scratch database to confirm the migrations table and the timestamp ordering match what your team expects.

Frequently asked questions

How do I install dbmate?

The README lists five routes: npm install --save-dev dbmate, brew install dbmate on macOS, a curl of the release binary into /usr/local/bin on Linux, scoop install dbmate on Windows, and the Docker image at ghcr.io/amacneil/dbmate. Each route ends with dbmate --help to confirm the binary runs.

How do I use dbmate to run a migration?

Set DATABASE_URL, run dbmate new to generate a timestamped SQL file in the migrations directory, edit it, then run dbmate up to create the database if needed and apply pending migrations. dbmate status shows what has been applied.

What is dbmate?

Dbmate is a standalone database migration tool that keeps a schema in sync across developers and production servers. It uses plain SQL migration files and works from Go, Node.js, Python, Ruby, PHP, Rust, C++ or any other language.

What is a DB migration tool?

It is software that applies ordered, versioned changes to a database schema and records which changes have already run. Dbmate does this by reading timestamped SQL files and tracking applied versions in a schema_migrations table.

How do I install dbmate on Linux?

The README downloads the release binary to /usr/local/bin/dbmate with curl, marks it executable with chmod +x, and then runs /usr/local/bin/dbmate --help. No package manager or runtime is required.

Official sources

  1. amacneil/dbmate on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
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/amacneil-dbmate.svg)](https://hysenlabs.com/projects/amacneil-dbmate)