Moving Django from runserver to production requires a WSGI server, reverse proxy, database, and proper configuration.

Production Settings Checklist

  # settings/production.py
import os

DEBUG = False
ALLOWED_HOSTS = os.environ.get("ALLOWED_HOSTS", "").split(",")
SECRET_KEY = os.environ["SECRET_KEY"]

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        "NAME": os.environ["DB_NAME"],
        "USER": os.environ["DB_USER"],
        "PASSWORD": os.environ["DB_PASSWORD"],
        "HOST": os.environ.get("DB_HOST", "localhost"),
        "PORT": os.environ.get("DB_PORT", "5432"),
    }
}

STATIC_ROOT = "/app/staticfiles"
MEDIA_ROOT = "/app/media"

SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = 31536000
  

Gunicorn WSGI Server

  pip install gunicorn
gunicorn mysite.wsgi:application --bind 0.0.0.0:8000 --workers 4
  

Worker count formula: (2 × CPU cores) + 1

Nginx Reverse Proxy

  server {
    listen 80;
    server_name example.com;

    location /static/ {
        alias /app/staticfiles/;
    }

    location /media/ {
        alias /app/media/;
    }

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
  

Collect Static Files

  python manage.py collectstatic --noinput
  

For cloud storage (S3):

  pip install django-storages boto3
  
  DEFAULT_FILE_STORAGE = "storages.backends.s3boto3.S3Boto3Storage"
STATICFILES_STORAGE = "storages.backends.s3boto3.S3StaticStorage"
AWS_STORAGE_BUCKET_NAME = "my-bucket"
  

Docker Deployment

  FROM python:3.12-slim

ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt gunicorn

COPY . .
RUN python manage.py collectstatic --noinput

EXPOSE 8000
CMD ["gunicorn", "mysite.wsgi:application", "--bind", "0.0.0.0:8000", "--workers", "4"]
  
  # docker-compose.yml
services:
  web:
    build: .
    ports:
      - "8000:8000"
    env_file: .env
    depends_on:
      - db
    command: >
      sh -c "python manage.py migrate &&
             gunicorn mysite.wsgi:application --bind 0.0.0.0:8000"

  db:
    image: postgres:16
    environment:
      POSTGRES_DB: mydb
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata:
  

Database Migrations in Production

  python manage.py migrate --noinput
  

Run migrations as a separate deployment step, not inside the web container startup in high-traffic environments.

Monitoring

  # settings.py
LOGGING = {
    "version": 1,
    "handlers": {
        "console": {"class": "logging.StreamHandler"},
    },
    "root": {
        "handlers": ["console"],
        "level": "WARNING",
    },
    "loggers": {
        "django": {"handlers": ["console"], "level": "INFO", "propagate": False},
    },
}
  

Add health check endpoint:

  from django.http import JsonResponse

def health(request):
    return JsonResponse({"status": "ok"})
  

Deployment Platforms

Platform Best For
Railway / Render Simple PaaS deployment
AWS (ECS/EB) Full AWS integration
Google Cloud Run Container-based, auto-scaling
DigitalOcean App Platform Managed containers

Production Django is battle-tested — Instagram, Pinterest, and Dropbox all run on it at massive scale.