dbmate
dbmate is a lightweight database migration tool designed to manage schema changes in a safe, repeatable, version-controlled way.
Instead of manually editing production databases, dbmate allows you to define schema changes as migration files that can be applied or reverted consistently across all environments.
Why Use dbmate?
Imagine adding a new column manually:
ALTER TABLE users ADD COLUMN phone TEXT;That works once.
But:
- what happens on staging?
- what happens in production?
- what if another developer forgets this change?
- what if you need to revert?
dbmate solves this by treating schema changes like source code.
Each migration is a versioned SQL file.
Installation
dbmate can be used either as a locally installed CLI tool or as a Docker container.
npm install -g dbmatedocker pull ghcr.io/amacneil/dbmate:latestVerify installation:
dbmate --versiondocker run --rm ghcr.io/amacneil/dbmate:latest --versionINFO
If installed via npm, dbmate can be used directly from your terminal.
Docker Setup
If you prefer containerized tooling, dbmate can also be executed through Docker without installing anything locally.
Docker Run
docker run --rm \
-v $(pwd)/db:/db \
--network host \
-e DATABASE_URL="postgres://app_user:strongpassword@localhost:5432/company_app?sslmode=disable" \
ghcr.io/amacneil/dbmate:latest upExplanation:
--rm→ removes container after execution-v $(pwd)/db:/db→ mounts local migration folderDATABASE_URL→ PostgreSQL connection stringup→ applies pending migrations
Docker Compose (Temporary Service)
Add this service only when needed:
services:
dbmate:
image: ghcr.io/amacneil/dbmate:latest
container_name: dbmate
restart: "no"
volumes:
- ./db:/db
environment:
DATABASE_URL: postgres://app_user:strongpassword@db:5432/company_app?sslmode=disable
depends_on:
- dbAfter use, it is common to comment it out:
# dbmate:
# image: ghcr.io/amacneil/dbmate:latest
# container_name: dbmate
# restart: "no"
# volumes:
# - ./db:/db
# environment:
# DATABASE_URL: postgres://app_user:strongpassword@db:5432/company_app?sslmode=disable
# depends_on:
# - dbBecause dbmate is not a long-running application service.
WARNING
Docker is often preferred in team environments because it avoids requiring local CLI installation and guarantees consistent tooling versions.
Migration Folder Structure
dbmate creates migrations inside:
db/migrations/Example:
db/
└── migrations/
├── 20260514103000_create_users.sql
├── 20260514120000_add_roles.sqlEach migration file contains:
-- migrate:up
SQL TO APPLY
-- migrate:down
SQL TO REVERTCreating a New Migration
Generate a migration file:
npx dbmate new create_usersdocker run --rm \
-v $(pwd)/db:/db \
ghcr.io/amacneil/dbmate:latest new create_usersResult:
db/migrations/20260514103000_create_users.sqlExample content:
-- migrate:up
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL
);
-- migrate:down
DROP TABLE users;Applying Migrations
Run all pending migrations:
npx dbmate updocker run --rm \
-v $(pwd)/db:/db \
--network host \
-e DATABASE_URL="postgres://app_user:strongpassword@localhost:5432/company_app?sslmode=disable" \
ghcr.io/amacneil/dbmate:latest updocker compose run --rm dbmate upThis:
- checks migration history
- applies only missing migrations
- updates migration tracking table
Rolling Back
Undo the most recent migration:
npx dbmate downdocker run --rm \
-v $(pwd)/db:/db \
--network host \
-e DATABASE_URL="postgres://app_user:strongpassword@localhost:5432/company_app?sslmode=disable" \
ghcr.io/amacneil/dbmate:latest downdocker compose run --rm dbmate downThis executes the SQL under:
-- migrate:downfor the latest migration.
Migration Tracking
dbmate automatically creates:
schema_migrationsinside your database.
This tracks already applied migrations.
Example:
| version |
|---|
| 20260514103000 |
| 20260514120000 |
This prevents duplicate execution.
Check Status
Shows pending/applied migrations:
dbmate statusDump Current Schema
Export current schema snapshot:
dbmate dumpUseful for:
- schema documentation
- CI validation
- backups of structure
Docker:
docker compose run --rm dbmate dumpBest Practices
- one logical schema change per migration
- always write both
upanddown - never edit already applied migrations
- create new migrations instead
- test migrations locally first
- keep migrations in version control
- use clear names (
add_user_roles, notfix_stuff) - backup production before major migrations
