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.

How migrations get skippedNew code deployedwith migration filesNo migrate stepor wrong databaseApp restartsexpects new schemaQueries failmissing columnsCode and schema must move together; migrate belongs in every deploy.
  1. See what is pending: python manage.py showmigrations --plan | grep "\[ \]".
  2. Back up, then apply: python manage.py migrate --noinput with production settings.
  3. Add migrate to your deploy pipeline before switching traffic to new code.
  4. Fail CI or deploys on pending migrations with python manage.py migrate --check.
  5. 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.

deploy order that keeps schema and code in step1Build or pull new codeincludes migration files2migrate --plan, then backupknow what will change3migrate --noinputbackward-compatible changes4Restart or switch app serversnew code, new schema5migrate --check passesnothing pendingMigrations run before the new code starts serving trafficdjango-deployment.com
Backward-compatible migrations let old code keep running while the schema changes.

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 --noinput after installing dependencies, before systemctl reload gunicorn.
  • Heroku: release: python manage.py migrate --noinput in the Procfile.
  • Docker Compose: a one-off migrate service or docker 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.

never ship code ahead of its schemamakemigrations --check in CImodel changes have migrationsmigrate in every deployonce, before the switchSame settings as the appright database, right envmigrate --check after deployfails loudly if pendingMigrations run in each web containerraces with replicasAutomation removes the step people forget at 6 pm on a Fridaydjango-deployment.com
Two check commands turn a silent warning into a failed pipeline.

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. migrate applies to default unless 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

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.

Keep going