Django-Syzygy: Safe Zero-Downtime Database Migrations

Search for a command to run...

No comments yet. Be the first to comment.
Master the core building blocks of Kafka, including partitions, consumer groups, and how to handle tricky topic migrations.

Avoiding Downtime in Django Migrations with django-migration-linter Deploying Django apps with zero downtime is essential in modern production systems, especially when rolling out updates on Kubernetes or deploying worker-based systems incrementally....

Building beautiful, responsive forms in Django doesn’t have to be a painful experience. By combining the power of django-tailwind with django-widget-tweaks and a few well-crafted custom CSS classes, you can create stunning, consistent forms that look...

What is HTMX? HTMX is a powerful tool that allows developers to create highly interactive and dynamic user interfaces without the need for heavy JavaScript frameworks. At its core, HTMX uses HTML attributes to define behaviour, making it an ideal com...

Django-Syzygy is a Django companion that makes database migrations safer in real production environments. It focuses on one specific, painful problem: migrations that are technically valid, but unsafe during rolling deployments.
Syzygy works with Django’s existing migration system and workflow. It doesn’t replace it, and it doesn’t require you to learn a new mental model. It quietly adds structure where Django is intentionally flexible, so deployments stop being a source of stress.
Consider a common change: removing a database field that’s no longer needed.
In development, this is trivial. In production, during a rolling deploy, it can break running instances that still expect that column to exist. Django doesn’t protect you from that class of failure.
Syzygy does.
The natural workflow:
You change your model (for example, remove a field)
Run makemigrations as usual
Syzygy detects a potentially unsafe change and asks for minimal input
It automatically splits the change into safe deployment stages
You keep using Django’s commands. Syzygy handles the staging.
Django migrations are excellent at describing what should change, but they don’t encode when a change is safe to apply relative to your application code.
A classic failure mode looks like this:
Without Syzygy:
You remove a field from a model
A migration drops the column
You deploy new code
Old application instances are still running
Migration runs → database column disappears → runtime errors
This is not a rare edge case. It’s a structural issue with rolling deployments.
With Syzygy:
The migration is automatically split into deployment-safe stages
Changes that old code can tolerate run first
Code is deployed
Breaking changes run only after old code is gone
The result is not “perfect safety,” but the elimination of an entire category of deployment-time failures.
Syzygy integrates with Django’s migration autodetector and migration system:
It analyzes generated migrations for deployment safety
It assigns or infers a stage (pre-deploy or post-deploy)
When necessary, it splits migrations automatically
It enforces consistency via Django system checks
It does not replace Django’s migration engine or alter database behavior. All migrations are still standard Django migrations.
# models.py
class MyModel(models.Model):
name = models.CharField(max_length=100)
# old_field removed
python manage.py makemigrations
Syzygy detects that removing a field is unsafe if old code is still running, and asks for a temporary default value so existing rows remain compatible.
It generates two migrations:
# 0001_pre_deploy.py
operations = [
syzygy.operations.AlterField(
model_name='mymodel',
name='old_field',
field=models.CharField(default='safe_default'),
),
]
# 0002_remove_field.py
operations = [
migrations.RemoveField(
model_name='mymodel',
name='old_field',
),
]
The first migration keeps old application instances working.
The second removes the field only after deployment.
class MyModel(models.Model):
name = models.CharField(max_length=100)
new_field = models.CharField(max_length=200)
Syzygy ensures the database can accept writes from old code:
# 0001_add_field.py (pre-deploy)
operations = [
migrations.AddField(
model_name='mymodel',
name='new_field',
field=models.CharField(
max_length=200,
db_default='temp_default',
),
),
]
# 0002_drop_db_default.py (post-deploy)
operations = [
syzygy.operations.AlterField(
model_name='mymodel',
name='new_field',
field=models.CharField(max_length=200),
stage=syzygy.constants.Stage.POST_DEPLOY,
),
]
The database default exists only to bridge the deployment window. Syzygy removes it once the new code is fully live.
(PostgreSQL is the recommended database; support for other backends depends on their DDL capabilities.)
Syzygy classifies operations based on one rule:
Old code must survive pre-deploy changes.
New code must survive post-deploy changes.
Typical classification:
Pre-Deploy:
Adding columns or tables
Adding indexes
Adding database defaults
Creating new constraints
Post-Deploy:
Removing or renaming fields
Dropping constraints
Destructive schema changes
If Syzygy can’t determine this safely, it fails early.
pip install django-syzygy
INSTALLED_APPS = [
# ...
'syzygy',
]
Optional configuration for edge cases:
from syzygy import Stage
MIGRATION_THIRD_PARTY_STAGES_FALLBACK = Stage.PRE_DEPLOY
MIGRATION_STAGES_OVERRIDE = {
'problematic_app.0001_bad_migration': Stage.POST_DEPLOY,
}
Nothing changes:
python manage.py makemigrations
python manage.py check
python manage.py migrate
# Apply safe changes
python manage.py migrate --pre-deploy
# Deploy application code
# Apply remaining changes
python manage.py migrate
If you forget --pre-deploy, migrate still works. The flag enables phased deployments; it doesn’t break existing pipelines.
The most valuable part of Syzygy in CI is simple:
Migration staging mistakes become CI failures instead of production incidents.
script:
- python manage.py check
- python manage.py makemigrations --dry-run --check
Syzygy’s system checks ensure migrations are internally consistent and safely staged.
This means a migration mixes safe and unsafe operations in a way that requires human intent.
You can:
Split the migration
Explicitly assign a stage
Override it in settings
The important part: the failure happens early, not during deployment.
Syzygy doesn’t replace good judgment:
Review generated migrations
Test migrations in staging
Prefer additive changes over destructive ones
Document complex transitions
Syzygy enforces structure; it doesn’t remove responsibility.
Django-Syzygy is most useful for:
Teams running rolling or blue-green deployments
Kubernetes/ ArgoCD / Docker environments
Applications with real uptime requirements
It’s probably unnecessary for:
Solo projects
Development-only environments
Apps that can afford downtime
Django-Syzygy makes deployment-unsafe migrations explicit and enforceable. It doesn’t promise perfection. It removes an entire class of avoidable production failures by encoding deployment reality directly into Django migrations.
You keep Django’s workflow.
You gain deployment safety.
And migration anxiety becomes a lot rarer.
That’s the whole point.