Roll Back a Django Migration in Production
Undo a Django migration safely in production: migrate an app back to a previous migration, understand which operations are reversible, handle data loss from reverting added columns and data migrations, roll back code and schema in the right order, and prefer forward fixes.
Problem statement
A release applied migration orders.0043_add_fulfillment_status, and something is wrong: a bug in the new code, a slow query on the new column, or a data migration that set wrong values. You want to undo it. Django can unapply migrations with migrate app_label previous_migration, but reversing is not always possible, can lose data written since, and must be coordinated with rolling back the code that depends on the schema.
- Find the target:
python manage.py showmigrations ordersand pick the last good migration (for example0042). - Check reversibility:
python manage.py sqlmigrate orders 0043 --backwardsprints the reverse SQL or fails for irreversible operations. - Decide the order: if old code cannot run with the new schema, roll back code and schema together; if it can, roll back code first and keep the schema.
- Unapply:
python manage.py migrate orders 0042. - Prefer a forward fix (a new migration) when reverting would destroy data written since the release.
Step-by-step solution
1. Pick the target and inspect
python manage.py showmigrations orders
python manage.py sqlmigrate orders 0043 --backwards
The backwards SQL shows what will happen. AddField reverses to DROP COLUMN (data in that column is lost), CreateModel to DROP TABLE, RenameField to a rename back. RunPython without a reverse_code and RunSQL without reverse_sql make the migration irreversible, and sqlmigrate --backwards reports an error.
2. Decide whether to revert the schema at all
Many problems do not need a schema rollback:
- New code is buggy, but the migration only added a nullable column: roll back the code, keep the column. Old code ignores it.
- A new index causes slow writes: drop it in a forward migration.
- A data migration set wrong values: write a forward data migration that corrects them.
Reverting the schema is necessary when the new schema breaks old code (renamed or removed columns, new NOT NULL columns without defaults) and you must run old code.
3. Coordinate with the code rollback
Two safe orders:
- Schema is backward compatible (old code works with new schema): deploy the old release first, then optionally unapply the migration later.
- Schema is not compatible: stop or scale down the new release, unapply the migration, deploy the old release. Expect a short outage or use maintenance mode.
Never unapply a migration while the new code that needs it is still serving traffic.
4. Unapply
python manage.py migrate orders 0042 --plan # preview
python manage.py migrate orders 0042
On PostgreSQL, each migration's reverse runs in a transaction unless it is marked atomic = False. Watch for locks: dropping a column is quick, but reversing an AlterField that rewrites a table holds an exclusive lock for the duration. Other apps whose migrations depend on orders.0043 are unapplied too; --plan shows them.
5. Clean up after
Remove or fix the migration file in the codebase, or keep it and add a new migration, so that the next deploy does not simply reapply the same change. If other environments already applied it, keep histories consistent: a migration that exists in some databases but not in the code causes InconsistentMigrationHistory or NodeNotFoundError later.
Explanation
Each Django migration records forward operations, and most built-in operations know their inverse. Unapplying runs those inverses in reverse dependency order and deletes rows from django_migrations. Structural changes reverse cleanly, but data does not come back: a dropped column's contents and rows created in a dropped table are gone. That is why safe teams design migrations to be backward compatible (expand, then contract later), so rolling back code does not require rolling back the schema.
Edge cases / notes
- Backups. Before reverting migrations that drop data, take a backup or snapshot; it is the only way back.
- Squashed migrations. You can unapply to a point inside a squash only if the replaced migrations still exist in the code.
- Third-party apps. Reverting a dependency upgrade may require unapplying its migrations first, before downgrading the package.
Next steps
- Plan full release rollbacks with Roll Back a Django Release Safely.
- Recover from a failed apply with Fix Django Migration Failed During Deploy.
- Avoid the need with the Django Migration Review Checklist.
FAQ
How do I undo the last Django migration?
Run python manage.py migrate app_label previous_migration_name, after checking the reverse with sqlmigrate --backwards.
How do I unapply all migrations of an app?
python manage.py migrate app_label zero. This drops the app's tables and their data.
Why does Django say a migration is irreversible?
It contains RunPython without reverse_code or RunSQL without reverse_sql. Add a reverse or write a forward fix.
Should I roll back the schema or the code first?
If old code works with the new schema, roll back code only. Otherwise stop the new code, unapply, then deploy old code.