Skip to content

Django Database Settings and Migrations

Database setup and migrations are the backbone of a stable Django project. If these two parts are done correctly, your app grows safely. If done carelessly, you can face downtime, broken deployments, or data loss.

This chapter gives you a practical and beginner-friendly path:

  • Configure database settings for SQLite, MySQL, and PostgreSQL
  • Understand migration flow and dependency order
  • Apply migrations safely in real projects
  • Handle migration conflicts and production risks

Before migration work, your database configuration must be correct. Django reads DATABASES from settings.py and uses it for all schema and query operations.

Use environment variables so the same code works for local, staging, and production. This avoids hardcoding secrets and makes deployments safer.

from pathlib import Path
import os
BASE_DIR = Path(__file__).resolve().parent.parent
DB_ENGINE = os.getenv("DB_ENGINE", "sqlite")

SQLite is perfect for learning, prototypes, and small apps. It is file-based, so setup is very fast and requires no DB server.

Install dependency

Terminal window
# No extra driver needed. SQLite support is built into Python.

Django config

from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
DATA_DIR = BASE_DIR / "_data"
DATA_DIR.mkdir(exist_ok=True)
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": DATA_DIR / "db.sqlite3",
}
}

One-file if/else pattern for all environments

Section titled “One-file if/else pattern for all environments”
DB_ENGINE = os.getenv("DB_ENGINE", "sqlite").lower()
if DB_ENGINE == "mysql":
DATABASES = {
"default": {
"ENGINE": "django.db.backends.mysql",
"NAME": os.getenv("DB_NAME"),
"USER": os.getenv("DB_USER"),
"PASSWORD": os.getenv("DB_PASSWORD"),
"HOST": os.getenv("DB_HOST"),
"PORT": os.getenv("DB_PORT", "3306"),
"OPTIONS": {"charset": "utf8mb4"},
}
}
elif DB_ENGINE in ["postgres", "postgresql", "pgsql"]:
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": os.getenv("DB_NAME"),
"USER": os.getenv("DB_USER"),
"PASSWORD": os.getenv("DB_PASSWORD"),
"HOST": os.getenv("DB_HOST"),
"PORT": os.getenv("DB_PORT", "5432"),
}
}
else:
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": BASE_DIR / "_data" / "db.sqlite3",
}
}

A Django migration is a Python file that records schema changes. When you change models.py, Django can generate migration files and apply them in order.

flowchart TD A[Edit models.py] --> B[python manage.py makemigrations] B --> C[Migration file created] C --> D[python manage.py migrate] D --> E[Schema updated in database]
Terminal window
python manage.py makemigrations
python manage.py migrate
python manage.py showmigrations
python manage.py sqlmigrate app_name 0001
CommandWhat it does
makemigrationsCreates migration files from model changes
migrateApplies unapplied migrations to DB
showmigrationsShows which migrations are applied/not applied
sqlmigrateDisplays raw SQL for one migration

Django creates and updates tables in dependency order. If one model depends on another (for example through ForeignKey), parent migration must run first.

class Category(models.Model):
name = models.CharField(max_length=120)
class Product(models.Model):
name = models.CharField(max_length=120)
category = models.ForeignKey(Category, on_delete=models.PROTECT)

In this case, Category table must exist before Product table.

class Migration(migrations.Migration):
dependencies = [
("products", "0001_initial"),
]
flowchart LR M1[products 0001_initial] --> M2[store 0002_add_product_fk] M2 --> M3[orders 0003_add_order_item]

Migration applying is simple in command form, but production-safe applying needs process discipline.

  1. Pull latest code and ensure a clean branch.
  2. Run python manage.py makemigrations --check --dry-run to verify no missing migration file.
  3. Run python manage.py showmigrations to inspect pending items.
  4. Run python manage.py migrate in staging first.
  5. Run smoke tests and critical workflows.
  6. Apply in production during a low-traffic window.
Terminal window
python manage.py migrate
python manage.py migrate app_name
python manage.py migrate app_name 0003
python manage.py migrate app_name zero
ActionCommand
Apply all pendingpython manage.py migrate
Apply one app onlypython manage.py migrate app_name
Roll back to specific migrationpython manage.py migrate app_name 0003
Unapply all app migrationspython manage.py migrate app_name zero

Not all migrations are equally safe. Some operations are low-risk; some can destroy data if done without backup.

High Risk

Dropping columns, dropping tables, changing data types without conversion.

Medium Risk

Renaming fields incorrectly, changing null/unique rules on dirty data.

Low Risk

Adding nullable columns, adding new tables, adding indexes.

  • Removing a field that still contains important data
  • Renaming a field by deleting old and adding new (instead of proper rename migration)
  • Converting CharField to IntegerField when old values are non-numeric
  • Setting null=False on a column that already has null rows

Common Migration Errors and Why They Happen

Section titled “Common Migration Errors and Why They Happen”
ErrorWhy it happensTypical fix
No migrations to apply but model changedYou forgot makemigrationsRun makemigrations and commit file
relation already existsManual SQL changed DB outside migration historyAlign state using --fake carefully
column does not existMigration history out of sync between environmentsCompare applied migration list and fix order
IntegrityError on migrateExisting rows violate new constraintsClean/fill data before constraint migration
Conflicting migrations detectedParallel branches created different migration headsCreate merge migration

How to Create Migrations That Are Easy on Existing Databases

Section titled “How to Create Migrations That Are Easy on Existing Databases”

When your app already has users and data, schema changes should be incremental. Avoid big destructive jumps.

Instead of adding non-null field directly:

  1. Add field with null=True and optional default.
  2. Write data migration to populate old rows.
  3. Change field to null=False in next migration.

This two-step approach prevents downtime and integrity failures.

If rename detection fails:

  1. Add new field.
  2. Copy data from old to new in RunPython migration.
  3. Update app code to use new field.
  4. Remove old field in a later release.

Migration Conflicts and How to Resolve Them

Section titled “Migration Conflicts and How to Resolve Them”

Conflicts usually happen when two branches create migrations from the same parent migration.

flowchart TD A[Branch A creates 0005] --> C[Git merge] B[Branch B creates 0005] --> C C --> D[Conflicting migrations detected] D --> E[python manage.py makemigrations --merge] E --> F[Create merge migration] F --> G[Test migrate on clean DB]
Terminal window
python manage.py showmigrations
python manage.py makemigrations --merge
python manage.py migrate

After creating a merge migration:

  • Review generated dependencies carefully
  • Run migrations on a fresh database
  • Run on a database snapshot with real-like data

Schema migrations change structure; data migrations change existing row values.

from django.db import migrations
def fill_order_status(apps, schema_editor):
Order = apps.get_model("orders", "Order")
Order.objects.filter(status__isnull=True).update(status="pending")
def reverse_fill_order_status(apps, schema_editor):
Order = apps.get_model("orders", "Order")
Order.objects.filter(status="pending").update(status=None)
class Migration(migrations.Migration):
dependencies = [
("orders", "0007_add_status"),
]
operations = [
migrations.RunPython(fill_order_status, reverse_fill_order_status),
]

Use custom SQL only when Django migration operations cannot express what you need. Examples: advanced indexes, triggers, and vendor-specific DB features.

from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("store", "0004_auto_20240601_1234"),
]
operations = [
migrations.RunSQL(
"""
INSERT INTO store_collection (title)
VALUES ('collection1')
""",
"""
DELETE FROM store_collection
WHERE title = 'collection1'
""",
)
]

Rollback is part of production safety. You should know exactly how to move backward when a release fails.

Terminal window
python manage.py migrate app_name 0003
Terminal window
python manage.py migrate app_name zero
  1. Take database backup (and verify restore process).
  2. Read migration SQL via sqlmigrate.
  3. Test migration on staging with recent production-like data.
  4. Estimate lock time for large table operations.
  5. Apply during low-traffic window.
  6. Monitor error logs and DB performance after deployment.
  7. Keep rollback command and backup restore steps ready.

Golden Rule

Small, reversible, well-tested migrations are better than one large risky migration.

Team Rule

Migration files are source code. Review them in pull requests like any critical code.

Beginner Rule

If unsure, test on a copy of real data first. Never guess in production.