Fix "relation already exists" When Running Django Migrations
Fix Django migrate failing with relation already exists, table already exists, or DuplicateTable: compare the schema with migration history, use migrate --fake or --fake-initial correctly, and avoid faking migrations that were never applied.
Problem statement
python manage.py migrate stops with:
django.db.utils.ProgrammingError: relation "orders_invoice" already exists
or on MySQL (1050, "Table 'orders_invoice' already exists"), sometimes column "status" of relation "orders_order" already exists. The database already contains what the migration is trying to create, but django_migrations says the migration has not run. History and schema disagree. That happens when tables were created outside migrations, a migration was applied and its record lost, migration files were regenerated, or the database was restored from a different environment.
- Find which migration fails and what it creates:
python manage.py sqlmigrate orders 0005. - Compare with the real schema (
\d orders_invoicein psql). - If the schema exactly matches the migration, record it as applied:
python manage.py migrate orders 0005 --fake. - For an existing database being brought under migrations for the first time, use
migrate --fake-initial. - If the schema differs, write a migration that reconciles it; never fake a change that is not really there.
- Take a backup before any
--fake.
Step-by-step solution
1. Identify the failing migration
The traceback shows Applying orders.0005_invoice... just before the error. Print its SQL:
python manage.py showmigrations orders
python manage.py sqlmigrate orders 0005
2. Compare with the database
python manage.py dbshell
\d orders_invoice
SELECT app, name, applied FROM django_migrations WHERE app = 'orders' ORDER BY id;
Check every column, type, nullability, index, and constraint the migration creates. Partial matches are common: the table exists, but a later column or index from the same migration does not.
3. Fake only what really exists
If the migration's whole effect is already present, record it without running it:
pg_dump -Fc -f /var/backups/before-fake.dump "$DATABASE_URL"
python manage.py migrate orders 0005 --fake
python manage.py migrate
--fake inserts the row into django_migrations and runs nothing. Use the specific app and migration name, not a bare migrate --fake, which marks every pending migration as applied, including ones whose changes do not exist, causing errors later that are much harder to diagnose.
4. Use --fake-initial for legacy databases
When adopting migrations for a database whose tables already exist (for example after inspectdb), run:
python manage.py migrate --fake-initial
Django checks that every table created by an initial = True migration exists, and fakes those migrations; it applies normally anything else. It only checks table and column names, not types or constraints, so review differences yourself.
5. Reconcile partial matches
If the table exists but a column does not, either finish the schema manually so it matches the migration and then fake it, or remove the conflicting object and let the migration create it (only if it holds no data you need). In team settings, writing a new migration with SeparateDatabaseAndState documents the reconciliation:
migrations.SeparateDatabaseAndState(
state_operations=[migrations.CreateModel(name="Invoice", fields=[...])],
database_operations=[], # table already exists in every environment
)
6. Prevent it
- Never create or alter tables by hand in environments managed by migrations.
- Do not regenerate migration files (deleting and running
makemigrationsagain) once they have been applied anywhere. - Restore databases together with the code version that matches their migration history.
Verification check: python manage.py migrate completes, python manage.py migrate --check exits 0, and python manage.py makemigrations --check --dry-run reports no changes, which confirms models, migrations, and history agree.
Explanation
Django decides what to run purely from django_migrations: it does not inspect the schema before applying a migration (except for --fake-initial). When the table already exists, the CREATE TABLE fails and the migration aborts. Recording the migration as applied with --fake aligns history with reality, but Django trusts you completely; if the schema does not actually match, later migrations and queries fail in confusing ways. Checking the real schema first is the step that makes --fake safe.
Edge cases / notes
- PostgreSQL transactional DDL. A failed migration on PostgreSQL rolls back entirely, so nothing is half-applied. MySQL does not roll back DDL; a partly applied migration needs manual cleanup.
- Indexes and constraints can also "already exist" when names collide; check
\diand constraint names. - Multiple databases. Use
--databasewhen faking on a non-default connection. - Test databases are built from migrations, so they never show this; it is always an environment-specific drift.
Next steps
- If history is out of order rather than ahead of the schema, see Fix Django InconsistentMigrationHistory.
- Make migrations routine with Django Migrations Deployment Strategy.
FAQ
What does migrate --fake do?
It marks migrations as applied in django_migrations without running their SQL.
Is migrate --fake safe?
Only for migrations whose changes really exist in the database. Fake one specific migration, not everything.
What is --fake-initial for?
Adopting migrations for an existing database: it fakes initial migrations when their tables already exist.
Why does PostgreSQL not leave a half-applied migration?
Its DDL is transactional, so a failed migration rolls back completely. MySQL does not offer this.