Fix "relation does not exist" in Django Production
Fix ProgrammingError: relation does not exist (and SQLite 'no such table') in Django production: unapplied or uncommitted migrations, the wrong database, faked migrations, and import-time queries.
Problem statement
After a deploy, a page or the whole site fails with:
django.db.utils.ProgrammingError: relation "orders_invoice" does not exist
LINE 1: SELECT "orders_invoice"."id", ... FROM "orders_invoice" ...
On SQLite the same problem reads OperationalError: no such table: orders_invoice, and for a missing column, column orders_invoice.due_date does not exist.
Django's models describe a table that the database does not have. Either the migration that creates it was never applied to this database, the migration does not exist in the deployed code, or the application is connected to a different database than you think. This guide identifies which one in a few commands.
- Run
python manage.py showmigrations <app>with production settings; unapplied migrations show[ ]. - If migrations are unapplied, run
python manage.py migrate(and add it to the deploy). - If
makemigrations --checkreports changes, the migration file was never created or committed; create it, commit it, deploy it. - Confirm the app and the migrate command use the same database: print
settings.DATABASES["default"]in both contexts. - If migrations are recorded as applied but the table is missing, they were faked; fix the history carefully.
- If
migrateitself fails with this error, find code that queries the database at import time.
Step-by-step solution
1. Check migration state with production settings
cd /srv/myapp/current
set -a; . /etc/myapp.env; set +a
.venv/bin/python manage.py showmigrations orders
.venv/bin/python manage.py migrate --plan
[ ] next to a migration means it is not applied to this database. migrate --plan lists what would run. If there are pending migrations, apply them:
.venv/bin/python manage.py migrate --noinput
Then make sure the deploy process runs migrate automatically before new code serves traffic, so this does not recur.
2. Check whether the migration file exists
.venv/bin/python manage.py makemigrations --check --dry-run
git status --short */migrations/
git log --oneline -- orders/migrations/ | head
If makemigrations --check reports changes, someone changed a model without generating a migration, or generated it locally and did not commit it. Generate it in development, review it, commit it, and deploy. Do not run makemigrations on the production server: the resulting file would exist only there and diverge from the repository.
Add makemigrations --check to CI so a model change without a migration fails the build.
3. Confirm which database the app uses
A common cause is migrate running against one database while Gunicorn connects to another, for example because the deploy script and the systemd unit load different environment files.
.venv/bin/python -c "
import django, os
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.production')
django.setup()
from django.conf import settings
db = settings.DATABASES['default']
print(db['ENGINE'], db.get('HOST'), db.get('PORT'), db['NAME'])
"
Run it in the deploy context, then compare with the environment the running service has:
sudo cat /proc/$(pgrep -f "gunicorn.*master" | head -1)/environ | tr '\0' '\n' | grep -E "DATABASE|DJANGO_SETTINGS"
In containers, check that the migration job or one-off container receives the same DATABASE_URL as the web container.
4. Look at the database directly
.venv/bin/python manage.py dbshell
\dt orders_*
SELECT app, name, applied FROM django_migrations WHERE app = 'orders' ORDER BY id DESC LIMIT 5;
SHOW search_path;
- Table missing and migration not recorded: step 1 applies.
- Table missing but the migration recorded as applied: the migration was faked (
migrate --fake) or the table was dropped manually. See step 5. - Table present in another schema: the connection's
search_pathdiffers (common with multi-tenant setups or customOPTIONS).
5. Repair a faked or broken history
If django_migrations says a migration is applied but its table is missing, roll the record back and apply for real. For the latest migration of an app:
.venv/bin/python manage.py migrate orders 0011 --fake # mark back to the migration before the missing one
.venv/bin/python manage.py migrate orders # apply 0012 for real
Only do this when you are sure the database state matches migration 0011. Take a backup first. If the history is tangled across several migrations, work through Fix InconsistentMigrationHistory in Django.
6. When migrate itself raises the error
If migrate crashes with relation does not exist before applying anything, some code queries the database while Django loads, before the tables exist. Typical examples:
# forms.py — evaluated at import time
class OrderForm(forms.Form):
region = forms.ChoiceField(choices=[(r.pk, r.name) for r in Region.objects.all()])
# apps.py
def ready(self):
Setting.objects.get_or_create(key="maintenance") # runs before migrations
Move queries into functions evaluated per request (for example ModelChoiceField(queryset=Region.objects.all()), which is lazy), and never query the database in AppConfig.ready(). Use a data migration or a post-migrate signal for initial data.
Verification check:
.venv/bin/python manage.py showmigrations | grep "\[ \]" || echo "all migrations applied"
.venv/bin/python manage.py makemigrations --check --dry-run
curl -sI https://example.com/orders/ | head -1
Explanation
Django keeps three things in sync: model classes in code, migration files that describe how to build the schema, and the django_migrations table recording which migration files have been applied to a specific database. "Relation does not exist" is the database reporting that the schema is behind the models. Finding which of the three links is broken (missing file, unapplied file, or a different database) tells you the fix.
Edge cases / notes
Multiple databases
With database routers, migrate only migrates the default database unless you pass --database. Apps routed to another database need migrate --database=analytics.
Old code, new schema during deploys
If migrations run after new code starts serving, requests briefly hit the old schema and fail. Run migrations before switching traffic, and keep them backward compatible so old code also works with the new schema.
When to automate this
Enforce makemigrations --check in CI, run migrate as a mandatory deploy step before the switch, and fail the deploy if showmigrations reports anything unapplied afterwards.
Next steps
- Build a safe migration process with Django Migration Strategy for Safe Deployments.
- Untangle migration records with Fix InconsistentMigrationHistory in Django.
- Recover a failed release with Django Migration Failed During Deploy: Recovery Playbook.
- For other server errors, see Fix Django 500 Internal Server Error in Production.
FAQ
What does "relation does not exist" mean in Django?
PostgreSQL has no table (relation) with that name in the schemas on the connection's search path. Usually the migration that creates it has not been applied.
Why does it work locally but not in production?
Your local database has the migration applied, or the migration file exists only on your machine. Commit the file and run migrate in production.
Should I run makemigrations on the production server?
No. Generate migrations in development, commit them, and only run migrate in production.
Why does migrate fail with relation does not exist?
Code runs a database query while Django starts, before the table is created. Remove queries from import time and AppConfig.ready().