Skip to main content

EZMig (Easy Migration) is a lightweight, SQL-first database migration tool designed for simplicity and CI/CD.

Project description

EZMig (Easy Migration)

EZMig is a lightweight, SQL-first database migration tool designed for simplicity and CI/CD.

It is:

  • simple
  • deterministic
  • database-agnostic
  • language-agnostic (just SQL)
  • easy to integrate into pipelines

โœจ Philosophy

EZMig follows a simple rule:

SQL is the source of truth.

No ORM. No schema diffing. No magic.

Just:

  • versioned SQL files
  • applied in order
  • tracked safely

๐Ÿš€ Features

  • โœ… Plain SQL migrations
  • โœ… Linear, predictable execution
  • โœ… Checksum validation (detect drift)
  • โœ… Repeatable migrations (views, functions)
  • โœ… Transaction support (when available)
  • โœ… Rollback support via --ezmig:rollback
  • โœ… Migration status tracking (pending, applied, changedโ€ฆ)
  • โœ… Replay / force apply for dev iteration
  • โœ… CLI-first design (CI/CD ready)
  • โœ… Migration scaffolding via ezmig new

๐Ÿ—„๏ธ Supported Databases

Database Driver Install Example
SQLite Built-in uv tool install ezmig examples/sqlite
PostgreSQL psycopg2 uv tool install 'ezmig[postgres]' examples/postgres
Oracle oracledb uv tool install 'ezmig[oracle]' examples/oracle

Install just what you need:

  • uv tool install ezmig โ€” SQLite support only
  • uv tool install 'ezmig[postgres]' โ€” PostgreSQL support
  • uv tool install 'ezmig[oracle]' โ€” Oracle support
  • uv tool install 'ezmig[all]' โ€” All databases
  • uvx ezmig --help โ€” run without installing

๐Ÿ“ฆ Installation

Linux one-line install

curl -fsSL https://raw.githubusercontent.com/klinvesta/ezmig/main/scripts/install.sh | BIN_DIR=$HOME/.local/bin sh

Standalone binary (no Python required)

Download a pre-built binary from the releases page.

uv tool install

uv tool install ezmig
uv tool install 'ezmig[postgres]'
uv tool install 'ezmig[oracle]'
uv tool install 'ezmig[all]'

uvx (run without installing)

uvx ezmig --help
uvx --from 'ezmig[postgres]' ezmig status --database dev
uvx --from 'ezmig[oracle]' ezmig plan --database dev

pipx

pipx install ezmig

pip

pip install ezmig                  # SQLite only
pip install 'ezmig[postgres]'      # + PostgreSQL
pip install 'ezmig[oracle]'        # + Oracle
pip install 'ezmig[all]'           # everything

See the Installation docs for full platform instructions and local binary builds.


๐Ÿงฐ Usage

ezmig new "create users"   # create versioned migration template
ezmig new "users_v" -r     # create repeatable migration template
ezmig status --database dev    # show current state for target
ezmig plan --database dev      # show pending migrations for target
ezmig apply --database dev     # apply migrations to target
ezmig apply --force --allow-replay --database dev   # force apply all (dev)
ezmig replay --migration "202601*.sql" --allow-replay --database dev
ezmig replay --repeatable "*.sql" --allow-replay --database dev
ezmig rollback --database dev  # rollback last migration on target

# inspect configured targets
ezmig config list
ezmig config show dev
ezmig config validate

๐Ÿ“ Project structure

migrations/
โ”œโ”€โ”€ versioned/
โ”‚   โ”œโ”€โ”€ 20260101000000_init.sql
โ”‚   โ””โ”€โ”€ 20260315093000_add_users.sql
โ””โ”€โ”€ repeatable/
    โ”œโ”€โ”€ my_function.sql
    โ””โ”€โ”€ my_view.sql

๐Ÿ“ Migration format

Versioned migration

-- ezmig:apply
CREATE TABLE users (
  id SERIAL PRIMARY KEY,
  email TEXT NOT NULL UNIQUE
);

-- ezmig:rollback
DROP TABLE users;
  • -- ezmig:apply โ†’ applied during ezmig apply
  • -- ezmig:rollback โ†’ used during rollback (ezmig rollback)
  • Required for rollback support

Repeatable migration

CREATE OR REPLACE VIEW active_users AS
SELECT * FROM users WHERE active = true;

๐Ÿ”ข Versioning

Versioned migrations

ezmig imposes no required filename format. Versioned migrations are sorted lexicographically by filename and executed in that order. You choose the naming convention.

Recommended convention: timestamps

For teams with more than one developer, the strongly recommended approach is a timestamp prefix:

20260323093000_add_payments_table.sql
20260323094512_add_audit_log.sql

Use YYYYMMDDHHmmss (14 digits). This works well because:

  • Timestamps are globally unique per developer per second with no coordination needed.
  • They sort correctly lexicographically, which is exactly how ezmig orders files.
  • No two developers on separate branches will accidentally pick the same prefix.
  • Merging branches never causes ordering conflicts โ€” each migration keeps its natural position in time.

Why not sequential numbers? 001, 002, 003 only work on a single branch. As soon as two developers independently create 003_foo.sql and 003_bar.sql on different branches, you have a conflict with no safe resolution. Timestamps eliminate this class of problem entirely.

Execution order

Files are sorted lexicographically by filename and applied strictly in that order. With a timestamp prefix, lexicographic order equals chronological order, so the behaviour is intuitive.

โš ๏ธ Never rename or edit a versioned migration file once it has been applied. ezmig stores the checksum of each file in ezmig_migrations. A rename looks like a missing file; a content change looks like drift. Both cause an error.

Immutability

Once a versioned migration has been applied, it is immutable:

  • Its checksum is recorded in ezmig_migrations.
  • If the file content changes, ezmig detects the mismatch during validation and refuses to proceed.
  • To modify the schema after the fact, add a new migration with a later timestamp.

Repeatable migrations

Repeatable migrations have no ordering constraints. They are:

  • Identified by their filename (used as the primary key in ezmig_migrations).
  • Tracked by checksum โ€” ezmig re-applies them whenever their content changes.
  • Always applied after all versioned migrations in a given ezmig apply run.
  • Sorted alphabetically among themselves.

Repeatable migrations are ideal for objects that can be safely recreated: views, functions, and stored procedures.


๐Ÿ“Š Migration states

ezmig classifies migrations internally:

  • PENDING โ†’ not yet applied
  • APPLIED โ†’ already executed
  • CHANGED โ†’ repeatable updated
  • UNCHANGED โ†’ repeatable unchanged
  • UNKNOWN โ†’ no adapter / offline mode

These states power both:

ezmig plan
ezmig status

๐Ÿงพ Migration table

ezmig tracks applied migrations using:

CREATE TABLE ezmig_migrations (
  version      VARCHAR PRIMARY KEY,
  type         VARCHAR NOT NULL,
  checksum     VARCHAR NOT NULL,
  applied_at   TIMESTAMP NOT NULL,
  execution_ms INTEGER
);

๐Ÿ”„ Rollback

Rollback is explicit and safe:

ezmig rollback        # rollback last migration
ezmig rollback -n 3   # rollback last 3 migrations
  • Executes -- ezmig:rollback blocks
  • Runs in reverse order
  • Wrapped in transactions (when supported)

๐Ÿ” Replay / force apply (dev)

For iterative local development, you can force re-apply migrations.

ezmig replay --migration "202601*.sql" --allow-replay --database dev
ezmig replay --repeatable "*.sql" --allow-replay --database dev
ezmig replay --all --allow-replay --database dev

# alias to replay everything in the target profile
ezmig apply --force --allow-replay --database dev

Safety gates:

  • replay/force is disabled by default
  • enable per-target with allow_replay = true in ezmig.toml, or pass --allow-replay

๐Ÿ” Validation

Before applying migrations, ezmig checks:

  • checksum integrity
  • missing migrations
  • order consistency

Use it directly:

ezmig validate

ezmig validate exits with:

  • 0 when validation passes
  • 1 when validation fails

Validation fails include:

  • applied versioned migration file missing on disk
  • checksum drift on applied versioned migration
  • out-of-order applied versioned migration history

Typical failure output:

Validation failed:
- Applied migration missing on disk: 20260101000000_init.sql
- Checksum drift detected for migration: 20260102000000_add_age.sql

Repeatable migration checksum changes are reported as warnings (they are expected to be re-applied on ezmig apply).


โš™๏ธ Configuration

ezmig.toml:

[default]
database = "dev"

[migration.default]
versioned = "./migrations/versioned"
repeatable = [
  "./migrations/repeatable",
  "./migrations/views",
  "./migrations/packages",
  "./migrations/gtt",
]

[database.dev]
url = "postgresql://user:pass@localhost:5432/db"
migration = "default"
categories = { env = "dev", location = "local" }

Target selection options for DB commands:

  • --database <name>
  • --category <key=value> (repeatable)
  • --group <name>

See docs/configuration.md for full schema and examples.


๐Ÿ”„ CI/CD Example

ezmig validate --database prod
ezmig plan --database prod
ezmig apply --database prod

Fail-fast CI pattern:

ezmig validate --database prod && ezmig apply --database prod

๐Ÿ“š Documentation

Project documentation is now organized under docs/ using MkDocs.

  • Site config: mkdocs.yml
  • Home: docs/index.md
  • Deployment guide: docs/deployment.md
  • Development guide: docs/development.md

Build docs locally:

uv sync --group docs
uv run --group docs mkdocs serve

๐Ÿงฑ Design goals

  • Keep the core small
  • Avoid abstraction layers
  • Favor explicit SQL over generated code
  • Make failures obvious and safe

โš ๏ธ Non-goals

ezmig intentionally does NOT:

  • โŒ generate schema diffs
  • โŒ manage ORM models
  • โŒ support complex migration graphs

Why EZMig?

Because schema evolution should be:

  • incremental
  • continuous
  • and quietly effective

๐Ÿ“œ License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ezmig-0.1.0b2.tar.gz (21.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ezmig-0.1.0b2-py3-none-any.whl (26.0 kB view details)

Uploaded Python 3

File details

Details for the file ezmig-0.1.0b2.tar.gz.

File metadata

  • Download URL: ezmig-0.1.0b2.tar.gz
  • Upload date:
  • Size: 21.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for ezmig-0.1.0b2.tar.gz
Algorithm Hash digest
SHA256 e0ba85052fa2ebfce71aac6c2a682948246755ca27bbd33404c53d393c2ec181
MD5 744c0e726a49143815a1da0834e361fc
BLAKE2b-256 fa0883f770734dc704642f7ed4ef13019615e9f79ebc31c20b435cbc5c745d9a

See more details on using hashes here.

Provenance

The following attestation bundles were made for ezmig-0.1.0b2.tar.gz:

Publisher: release.yml on klinvesta/ezmig

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file ezmig-0.1.0b2-py3-none-any.whl.

File metadata

  • Download URL: ezmig-0.1.0b2-py3-none-any.whl
  • Upload date:
  • Size: 26.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for ezmig-0.1.0b2-py3-none-any.whl
Algorithm Hash digest
SHA256 abf64aca85b5b85add372199f6c7f125a052b4d4f5d935ef8d456efb624aad37
MD5 11e1f8000285260fc0e93c063e3c6376
BLAKE2b-256 af04cf9eee53c0103937ad5bcc7515d61fef43162eff3b0ae6d7ffdd9d834cf3

See more details on using hashes here.

Provenance

The following attestation bundles were made for ezmig-0.1.0b2-py3-none-any.whl:

Publisher: release.yml on klinvesta/ezmig

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page