# pgmigrate keeps PostgreSQL schema changes inside one transaction when you want it

> pgmigrate is Yandex's Python migration tool for PostgreSQL, built around plain SQL migration files with callbacks and a choice between transactional and non-transactional steps. It suits teams that want their schema evolution to be a versioned, rollback-safe artefact.

**yandex/pgmigrate** — Simple tool to evolve PostgreSQL schema easily.

- Repository: https://github.com/yandex/pgmigrate
- Stars: 671 · Forks: 60
- Language: Python
- License: PostgreSQL
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/yandex-pgmigrate

## Plain SQL files with a version, not a model diff

PGmigrate is a database migration tool developed by Yandex, described as a simple tool to evolve a PostgreSQL schema easily, and the design centre of it is that migrations are files you write. The project gives you the mechanics of applying them in order, recording which version the database is at, and reporting what has or has not been applied, and it leaves the SQL entirely to you. That is a clearly different model from the tools that take your ORM models and diff them to produce migrations, and the difference shows up in review. With a diffing tool, the migration is generated output and a human reads it to check it. With pgmigrate, the migration is source: you can write a data backfill inside the same file, you can write a comment explaining why the column was added, and you can hand the file to a DBA. The trade is that you write more by hand, and the tool does less for you. The audience is a team with PostgreSQL and version control, where the schema change is treated as a reviewable artefact rather than as a side effect of a deployment.

## Transactional and non-transactional migrations, and why both exist

The first key feature is that you can use the whole power of PostgreSQL DDL, with the choice of transactional and non-transactional migrations. PostgreSQL has a well known constraint on this. Some statements, notably ones that touch the schema concurrently or work on indexes, cannot run inside a transaction block. A tool that insists on wrapping everything in one transaction simply cannot migrate those cases, and a tool that wraps nothing loses the safety of atomicity. pgmigrate lets you pick per migration. The second and third features build on that. Online migrations let you execute a series of transactional migrations and callbacks in a single transaction, so if something goes wrong a simple ROLLBACK brings you back to a consistent state. Callbacks let you run some DDL at specific steps of the migration process, and the example given in the documentation is dropping some code before executing migrations and creating it back after they were applied. That is a pattern for a schema change that a running application cannot survive in one state, and it only works if the tool can sequence the work for you.

## The version history shows a decade of small, specific fixes

The release history in the project documentation is a good guide to what this tool actually is. The first open source version is 1.0.0 on 2016-10-03, and the current version is 1.0.13, released on 2026-08-31. Between them the changes are small and specific, which is what you would expect from a tool whose job is to apply files in order. Version 1.0.10 added the option to allow mixing transactional and non-transactional migrations, which is the same feature area as the primary design decision. Version 1.0.12 disabled sqlparse grouping limits to allow large insert migrations, and 1.0.11 switched to using psycopg2 commit and rollback to avoid messing up its state, which is a fix for connection handling rather than a new capability. Version 1.0.9 added an option to show only unapplied migrations in the info command, and 1.0.8 allowed reordering the setting of the schema version and the afterEach callback, which is a direct continuation of the callback feature. Earlier entries show subdirectories in the migrations folder being allowed, a version gaps check, blocking pids termination, and the use of application_name instead of backend pid for conflict termination. The last push to the repository was on 2026-09-19 and it is not archived, so the tool is still being worked on.

## Installing it and running the test suite

Installation is a single pip command:

```bash
pip install yandex-pgmigrate
```

The package name on PyPI is yandex-pgmigrate, and the setup metadata lists three runtime requirements, sqlparse at 0.5.5 or newer, psycopg2 at 2.9.11 or newer, and PyYAML at 6.0.3 or newer, which tells you the shape of the tool. sqlparse is the SQL parser, psycopg2 is the PostgreSQL driver, and PyYAML is what reads the configuration and the migration metadata. The tests are the interesting part to read, because they tell you what the tool needs from a database. Tests require a running PostgreSQL instance with superuser, so that it can create and drop databases, and there are two ways to provide one. You can set it up yourself and use tox:

```bash
tox
```

Or you can use Docker with the make target:

```bash
make test
```

The Makefile shows what that target does, building a Docker image tagged pgmigrate and running it, and the Dockerfile is the test environment in full. It is based on ubuntu:resolute, creates an explicit postgres user and group with uid and gid 999, generates the en_US.UTF-8 locale so that PostgreSQL is utf-8 enabled by default and sets LANG accordingly, installs postgresql-common, and then pulls PostgreSQL 18 from the apt.postgresql.org.sh repository with PG_MAJOR set to 18. It also installs libpq-dev, python3-venv, python3-dev and build-essential for the driver build, creates a virtualenv at /venv with tox installed into it, and finally runs the run_test.sh script that sits at the repository root. The superuser requirement for the tests is worth remembering, because it tells you the tool expects to own the database it migrates.

## The repository layout and where the detail lives

The project is a single script with supporting directories rather than a package with a module tree, and that reflects its age and its scope. The top level holds pgmigrate.py, the main script, plus setup.py, a Makefile, a tox.ini, a run_test.sh, an AUTHORS file and a Dockerfile. There is a doc/ directory containing the tutorial that the documentation points to as the complete manual, and a features/ directory, which in a project like this usually holds the test specifications for each behaviour, in this case the behaviour-driven style the project uses for its features. The tooling files at the root give a good sense of the standards applied. There is an .isort.cfg and a .pylintrc, so import order and linting are configured rather than left to taste, and a .github/ directory holds the workflow that produces the build status badge, with the test job named Test. The licence situation is unusual in a useful way. The project is distributed under the PostgreSQL licence rather than one of the standard open source licences, and the setup.py header carries the full permission and warranty disclaimer text, with the copyright attributed to Yandex LLC for 2016 to 2026 and to the other contributors listed in the AUTHORS file. The PostgreSQL licence is permissive in effect, but it is a bespoke grant rather than MIT or Apache, so read the LICENSE file before you vendor it.

## Where pgmigrate is the wrong choice

The clearest case for a different tool is a team that wants the schema derived from code. If your models are the source of truth and you would rather not maintain migration files by hand, pgmigrate gives you nothing for that, because it consumes files you write rather than diffing your models. The second case is the data migration problem. pgmigrate applies SQL, and a SQL file can contain a data backfill, but there is no data migration framework here, no separate concept of a data migration with its own retry semantics, and no way to express a multi-step backfill that has to be resumable. If your schema change is mostly data movement, you will write that logic yourself in SQL and lose the safety a dedicated tool would give you. The third case is operational scale. This is a single script that connects to one database and applies files in order. There is no coordination across a fleet of databases, no scheduling, no dashboard and no audit trail beyond the schema version recorded in the database itself. And there is a real constraint in the transactional mode, which is inherent to PostgreSQL rather than a defect in the tool. If your migration uses constructs PostgreSQL refuses in a transaction block, you cannot use the online migration feature for that step, and the mixed mode added in version 1.0.10 exists precisely because of that tension. The alternative for that case is a tool with per-statement transaction control, or simply accepting that some migrations run outside a transaction, which pgmigrate supports.

## Conclusion

Adopt pgmigrate if you want schema evolution kept as plain SQL files under version control with a recorded schema version, and you want the option of applying a batch of migrations in a single transaction that you can roll back. Do not adopt it if you need automatic diffing of models, a data migration framework or multi-database orchestration, because the tool operates on migration files you write yourself. Before you adopt it, read the tutorial in doc/tutorial.md, check that your migration files avoid constructs PostgreSQL refuses inside a transaction block if you plan to use the transactional mode, and confirm that the PostgreSQL version you run is supported by the version of pgmigrate you install.

## FAQ

### What is PGmigrate and who develops it?

It is a database migration tool for PostgreSQL, developed by Yandex, described as a simple tool to evolve a PostgreSQL schema easily. Migrations are plain SQL files that the tool applies in order and records the schema version for.

### How do I install PGmigrate?

With `pip install yandex-pgmigrate`. The package requires sqlparse 0.5.5 or newer, psycopg2 2.9.11 or newer, and PyYAML 6.0.3 or newer.

### What are the transactional and online migration options in PGmigrate?

You can mix transactional and non-transactional migrations, and online migrations let you execute a series of transactional migrations and callbacks in a single transaction, so a simple ROLLBACK returns you to a consistent state if something goes wrong. Callbacks let you run DDL at specific steps of the migration process.

### How do I run the PGmigrate tests?

The tests need a running PostgreSQL instance with superuser rights, so that databases can be created and dropped. You can set one up yourself and run `tox`, or use Docker with `make test`, which builds the pgmigrate image and runs it.

### What licence is PGmigrate released under?

It is distributed under the PostgreSQL licence, with the terms in the LICENSE file and the full permission and warranty disclaimer text in the setup.py header. The copyright is held by Yandex LLC for 2016 to 2026 and by the other contributors named in the AUTHORS file.

## Sources

- [Issues](https://github.com/yandex/pgmigrate/issues)
- [License: PostgreSQL](https://github.com/yandex/pgmigrate/blob/master/LICENSE)
- [README](https://github.com/yandex/pgmigrate/blob/master/README.md)
- [yandex/pgmigrate on GitHub](https://github.com/yandex/pgmigrate)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/yandex-pgmigrate
