# django-pg-zero-downtime-migrations: a Postgres backend swap, not a migration library

> django-pg-zero-downtime-migrations is a drop-in Django PostgreSQL backend that applies migrations with respect to database locks, by setting lock and statement timeouts and by dropping constraints explicitly. The README is blunt about the cost: migrations do not run in transactions, so a failed migration can leave the database in an intermediate state, and the package is described as beta.

**tbicr/django-pg-zero-downtime-migrations** — Django postgresql backend that apply migrations with respect to database locks

- Repository: https://github.com/tbicr/django-pg-zero-downtime-migrations
- Stars: 574 · Forks: 31
- Language: Python
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/tbicr-django-pg-zero-downtime-migrations

## The switch is a database ENGINE setting

The package installs like any other Python distribution:

```bash
pip install django-pg-zero-downtime-migrations
```

and it is enabled by pointing your database configuration at the backend it provides, along with the settings that make the behaviour safe:

```python
DATABASES = {
    'default': {
        'ENGINE': 'django_zero_downtime_migrations.backends.postgres',
        #'ENGINE': 'django_zero_downtime_migrations.backends.postgis',
        ...
    }
}
ZERO_DOWNTIME_MIGRATIONS_LOCK_TIMEOUT = '2s'
ZERO_DOWNTIME_MIGRATIONS_STATEMENT_TIMEOUT = '2s'
ZERO_DOWNTIME_MIGRATIONS_FLEXIBLE_STATEMENT_TIMEOUT = True
ZERO_DOWNTIME_MIGRATIONS_RAISE_FOR_UNSAFE = True
```

That is the whole integration. The engine string changes, four settings are added, and the rest of your project is untouched. A PostGIS variant of the backend is available on the commented line, so spatial Django projects are covered too.

The design choice worth naming is that this is a backend, not a migration tool. You are not writing a different kind of migration, and there is no new migration command, and the README says that for any other purpose the backend works the same as the standard Django backend. The zero downtime improvements are scoped precisely: they apply to schema migrations and to RunSQL operations, and the note says explicitly that they do not apply to RunPython operations. For everything else, this is the ordinary PostgreSQL backend with a different engine path.

That scoping is honest, and it is also the first thing to check against your own migration history, because a project with a lot of data migrations gets no benefit from it.

## Timeouts are the mechanism, not a wrapper around it

The guarantees come from two PostgreSQL settings that the backend applies to specific statements rather than globally.

`ZERO_DOWNTIME_MIGRATIONS_LOCK_TIMEOUT` applies lock_timeout to SQL statements that require an ACCESS EXCLUSIVE lock, which is the lock that a large table rewrite needs and the one that queues every other query behind it. The default is None, meaning the current PostgreSQL setting is used, and any other value applies that timeout, with 0 and its equivalents disabling the timeout entirely.

`ZERO_DOWNTIME_MIGRATIONS_STATEMENT_TIMEOUT` does the same for statement_timeout on the same class of statement, again defaulting to None.

`ZERO_DOWNTIME_MIGRATIONS_FLEXIBLE_STATEMENT_TIMEOUT` handles the other half of the problem. When statement_timeout is enabled globally, a long running operation such as index creation or constraint validation will be killed by it even though it does not hold the dangerous lock. This option sets statement_timeout to 0ms for statements that require a SHARE UPDATE EXCLUSIVE lock, which is exactly the lock level used by those operations. The default is False.

So the mechanism is precise: a statement that would block the whole database is given a short leash and fails fast, while a statement that is merely long is allowed to finish. A migration that would have taken the service down now raises a timeout error, which is a considerably better outcome than a queue of blocked queries, but it is still a failed migration and it has to be dealt with.

`ZERO_DOWNTIME_MIGRATIONS_RAISE_FOR_UNSAFE`, defaulting to False, is the guard against the cases the tool cannot make safe. Enabled, it does not allow a potentially unsafe migration to run at all.

## No transactions for migrations, and the bill that comes with it

The most important paragraph in the README is the one about differences with the standard Django backend, and it is a trade rather than a free improvement.

The backend does not use transactions for migrations, with the exception of the RunPython operation. The stated reason is that not all SQL fixes can be run in a transaction, and that avoiding transactions allows it to avoid deadlocks for complex migrations. The consequence is spelled out: when your migration fails in the middle of the file, you need to fix the database state manually instead of potentially causing downtime. The recommended practice follows from that, which is to make migration modules as small as possible.

That is a substantial behavioural difference from stock Django, where a failed migration rolls back. A team that has never had to think about partially applied migrations will need to, and the README is clear that this is the deal.

The mitigating setting is `ZERO_DOWNTIME_MIGRATIONS_IDEMPOTENT_SQL`, which defaults to False. Set to True, it skips SQL that has already been applied, so a failed migration can be rerun after the underlying issue is fixed, such as a data problem or a long running query. The cost of that safety is stated too, and it is a good catch: idempotent mode checks rely only on the name and the valid state of indexes and constraints, so it can ignore name collisions, and the README says it is not recommended to use it for CI checks.

The other two settings are narrower. `ZERO_DOWNTIME_DEFERRED_SQL` controls when deferred SQL runs, with True running it in a similar way to the default Django behaviour and False running it as soon as possible. `ZERO_DOWNTIME_MIGRATIONS_EXPLICIT_CONSTRAINTS_DROP`, defaulting to True, drops foreign keys, unique constraints and indexes before dropping a table or a column, so that the drop does not fail on dependent objects.

## Zero downtime is a deployment contract, not a setting

The deployment flow section is the part that makes the whole idea coherent, and it starts with a list of requirements rather than a command.

There has to be one database. There have to be several instances of the application, and the application must always be available even if you restart one of the instances. There has to be a balancer in front of the instances. The application has to work before, during and after the migration, so the old application works with both the old and the new database schema version. And the application has to work before, during and after the instance update, so the old and new application versions both work with the new schema version.

Then the flow is two steps. Apply the migrations, and then disconnect an instance from the balancer, restart it and put it back, repeating that operation one by one for all instances.

The reason for the dance is that adding a nullable column is safe while old code runs, but dropping one is not, and rolling instances one at a time is what keeps the old code path alive against the new schema. If a deployment does not satisfy those rules, the README says to split it into smaller deployments.

That is the real work of a zero downtime migration, and the package is only the part that makes the schema change itself survivable. A team with one instance and no balancer gets no benefit, and the tool's timeouts will simply turn a blocking migration into an error.

## Version support, release cadence and the beta caveat

The compatibility surface is broad and the README shows it as badges. PostgreSQL 14, 15, 16, 17 and 18 are supported. Django 4.2, 5.0, 5.1, 5.2, 6.0 and 6.1 appear in the project classifiers, and the runtime requirement is Python 3.8 or later with Django 4.2 or later, with classifiers running from Python 3.8 up to 3.14.

The release history shows a project that keeps up with the ecosystem. Version 0.20 from 2026-08-26 brought Python 3.14, Django 6.0 and 6.1, and PostgreSQL 18 support, along with a bug fix, a move to uv, ruff and pyproject.toml. Version 0.19 from 2025-06-06 added Django 5.2 support, and 0.18 from 2025-03-16 fixed the deferrable parameter for a unique constraint.

That last entry is instructive about the kind of bug this backend deals with. Getting a deferrable parameter right on a unique constraint is not a style question, it is the difference between a migration that works and one that fails halfway.

Two caveats from the README should shape your expectations. The package is described as being in beta, with a request to check the migrations SQL before applying anything in production and to submit an issue for questions. And the backend provides the same result state as the standard one, except in the case of using `ZERO_DOWNTIME_MIGRATIONS_KEEP_DEFAULT=True` on Django older than 5.0.

The licence is MIT, the project metadata classifies it as Production/Stable, and the last push was on 2026-08-26. The package is on PyPI as django-pg-zero-downtime-migrations, and the changelog is kept in CHANGES.md in the repository.

## Conclusion

Adopt django-pg-zero-downtime-migrations if you deploy Django on PostgreSQL with several instances behind a balancer, where a table lock during a migration takes the service down, and your team can accept reviewing the generated SQL. Do not adopt it if you are migrating a single instance in development, where the timeouts only get in the way, or if your process depends on migrations being atomic, since the backend deliberately gives that up. Verify first which PostgreSQL versions and Django versions you are on, since the badges cover PostgreSQL 14 to 18 and Django 4.2 to 6.1, that your deployment satisfies the five conditions the README lists, and that the migrations you plan to run are the safe kind, because the README asks you to check the migration SQL before applying it in production.

## FAQ

### How do I enable zero downtime migrations in Django?

Install the package, set the ENGINE of your default database to django_zero_downtime_migrations.backends.postgres, and add the safe settings, typically ZERO_DOWNTIME_MIGRATIONS_LOCK_TIMEOUT, ZERO_DOWNTIME_MIGRATIONS_STATEMENT_TIMEOUT, ZERO_DOWNTIME_MIGRATIONS_FLEXIBLE_STATEMENT_TIMEOUT and ZERO_DOWNTIME_MIGRATIONS_RAISE_FOR_UNSAFE. A postgis backend is provided as well.

### Does django-pg-zero-downtime-migrations run migrations in a transaction?

No, except for RunPython operations. The README gives the reason, that not all SQL fixes can be run in a transaction and that avoiding transactions avoids deadlocks, and the consequence, that a migration failing midway can leave the database in an intermediate state that has to be fixed manually. It recommends keeping migration modules small.

### What does ZERO_DOWNTIME_MIGRATIONS_IDEMPOTENT_SQL do?

It enables idempotent mode, which skips already applied SQL so a failed migration can be rerun after the issue is fixed. The README notes that the checks rely only on names and the valid state of indexes and constraints, so name collisions can be ignored, and that it is not recommended for CI checks.

### What does a zero downtime deployment require with this backend?

The README lists one database, several application instances that stay available individually, a balancer in front of them, and an application that works before, during and after the migration with both the old and the new schema. The flow is to apply the migrations and then disconnect, restart and reattach each instance in turn, and if those conditions are not met the deployment should be split into smaller ones.

## Sources

- [Issues](https://github.com/tbicr/django-pg-zero-downtime-migrations/issues)
- [License: MIT](https://github.com/tbicr/django-pg-zero-downtime-migrations/blob/master/LICENSE)
- [README](https://github.com/tbicr/django-pg-zero-downtime-migrations/blob/master/README.md)
- [Releases](https://github.com/tbicr/django-pg-zero-downtime-migrations/releases)
- [tbicr/django-pg-zero-downtime-migrations on GitHub](https://github.com/tbicr/django-pg-zero-downtime-migrations)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/tbicr-django-pg-zero-downtime-migrations
