Fix "You have unapplied migrations" in Production
Fix the Django warning You have N unapplied migration(s) on a production server: run migrate in the deploy pipeline, check which migrations are pending, handle migrations missing from the repo, and stop the warning from hiding real problems.
Problem statement
Running any management command on the server prints:
You have 3 unapplied migration(s). Your project may not work properly until you apply
the migrations for app(s): orders, accounts.
Run 'python manage.py migrate' to apply them.
and some pages fail with relation does not exist or column does not exist. The code deployed to the server includes migration files that have not been run against the production database. Usually the deploy process copies new code and restarts the app but has no migrate step, or the step runs against a different database or settings module than the app uses.
- See what is pending:
python manage.py showmigrations --plan | grep "\[ \]". - Back up, then apply:
python manage.py migrate --noinputwith production settings. - Add
migrateto your deploy pipeline before switching traffic to new code. - Fail CI or deploys on pending migrations with
python manage.py migrate --check. - If a migration is pending because a file is missing or renamed, fix history instead of faking blindly.
Step-by-step solution
1. List pending migrations
python manage.py showmigrations --plan | grep "\[ \]"
python manage.py migrate --plan
--plan prints the exact operations migrate would run. Read it before applying anything in production, especially RemoveField, DeleteModel, or RunPython steps.
2. Confirm you are on the right database
python manage.py shell -c "from django.db import connection; print(connection.settings_dict['HOST'], connection.settings_dict['NAME'])"
A common surprise: the shell loads settings.development with SQLite while Gunicorn uses production settings with PostgreSQL. Load the same environment file and DJANGO_SETTINGS_MODULE that the service uses before running migrate.
3. Back up and apply
pg_dump -Fc -f /var/backups/pre-migrate-$(date +%F-%H%M).dump "$DATABASE_URL"
python manage.py migrate --noinput
For large tables, check whether any migration rewrites a table or takes long locks; see Django Schema Changes on Large Tables.
4. Put migrate in the pipeline
Whatever deploys your code should run migrations exactly once per release:
- Shell or Fabric scripts:
python manage.py migrate --noinputafter installing dependencies, beforesystemctl reload gunicorn. - Heroku:
release: python manage.py migrate --noinputin the Procfile. - Docker Compose: a one-off
migrateservice ordocker compose run --rm web python manage.py migrate. - Kubernetes: a Job or Helm pre-upgrade hook.
Running it in every container's entrypoint works for one instance but races with several.
5. Guard against forgetting
python manage.py migrate --check # exits non-zero if migrations are pending
python manage.py makemigrations --check --dry-run # exits non-zero if models changed without a migration
Run the second in CI so model changes cannot merge without migration files, and the first as a post-deploy check or health check during rollout.
6. When a pending migration should not be pending
If showmigrations lists a migration as unapplied but its changes already exist in the database (applied manually, or from a branch that was later rewritten), migrate fails with "already exists". Do not simply --fake everything; check carefully and see Fix "relation already exists" When Running Django Migrations.
Verification check: python manage.py migrate --check exits 0, showmigrations shows [X] for every migration, and the pages that failed now load.
Explanation
Django records applied migrations in the django_migrations table. On startup of a management command, it compares the migration files on disk with that table and warns about any that are missing from the table. The app itself does not refuse to run, because Django cannot know whether pending migrations matter for the current request. That is why the warning is easy to ignore and why the real symptom is often a database error on a specific page, long after the deploy that introduced the migration.
Edge cases / notes
- Multiple databases.
migrateapplies todefaultunless you pass--database; routers decide which apps go where. - Third-party apps. Upgrading a package can add migrations (for example
django-celery-beat); they need applying too. - Squashed migrations. If both the squashed and original files exist, Django handles them; do not delete originals until every environment has applied the squash.
- Long migrations may exceed deploy timeouts; run them as a separate, monitored step.
Next steps
- Plan safe ordering with Django Migrations Deployment Strategy.
- If
migrateitself fails, see Fix a Django Migration That Failed During Deploy.
FAQ
Is it safe to run migrate on a live production site?
Usually, if migrations are backward compatible and you have a backup. Review migrate --plan first.
Does Django apply migrations automatically on deploy?
No. Something in your deploy process must run python manage.py migrate.
How do I fail a deploy if migrations are pending?
Run python manage.py migrate --check, which exits non-zero when anything is unapplied.
Should I run migrate in the Docker entrypoint?
Only with a single instance. With replicas, run it once as a separate step.