django-deployment

SKILL.md

Django Deployment

You are a Django deployment expert. Your goal is to help get Django apps running reliably in production.

Initial Assessment

Check for project context first: If .agents/django-project-context.md exists, read it for the hosting platform, web server, containerization approach, and environment variable strategy.


Settings Structure (Split Settings Pattern)

config/settings/
├── __init__.py
├── base.py      # Shared settings
├── dev.py       # Development overrides
├── test.py      # Test overrides
└── prod.py      # Production overrides
# base.py
from pathlib import Path
import environ

env = environ.Env()
BASE_DIR = Path(__file__).resolve().parent.parent.parent

# Read .env file if present
environ.Env.read_env(BASE_DIR / '.env')

SECRET_KEY = env('SECRET_KEY')
DEBUG = env.bool('DEBUG', default=False)
ALLOWED_HOSTS = env.list('ALLOWED_HOSTS', default=[])

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # Third-party
    'rest_framework',
    # Local
    'apps.users',
    'apps.articles',
]

DATABASES = {
    'default': env.db('DATABASE_URL', default='sqlite:///db.sqlite3')
}

# Static & Media
STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'staticfiles'
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'
# prod.py
from .base import *

DEBUG = False
ALLOWED_HOSTS = env.list('ALLOWED_HOSTS')

# Security headers
SECURE_SSL_REDIRECT = True
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
X_FRAME_OPTIONS = 'DENY'
SECURE_CONTENT_TYPE_NOSNIFF = True

# Static files with WhiteNoise
MIDDLEWARE.insert(1, 'whitenoise.middleware.WhiteNoiseMiddleware')
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'

# Logging
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console': {'class': 'logging.StreamHandler'},
    },
    'root': {
        'handlers': ['console'],
        'level': 'WARNING',
    },
    'loggers': {
        'django': {'handlers': ['console'], 'level': 'WARNING', 'propagate': False},
    },
}

Environment Variables

Required Production Variables

# .env.example (commit this — not .env)
SECRET_KEY=your-secret-key-here
DEBUG=False
ALLOWED_HOSTS=example.com,www.example.com
DATABASE_URL=postgres://user:pass@host:5432/dbname
REDIS_URL=redis://localhost:6379/0

# Optional
EMAIL_URL=smtp://user:pass@smtp.example.com:587
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_STORAGE_BUCKET_NAME=
SENTRY_DSN=

Dockerfile

FROM python:3.12-slim

# Environment
ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1 \
    PIP_NO_CACHE_DIR=1

WORKDIR /app

# Install system deps
RUN apt-get update && apt-get install -y \
    libpq-dev gcc \
    && rm -rf /var/lib/apt/lists/*

# Install Python deps
COPY requirements.txt .
RUN pip install --upgrade pip && pip install -r requirements.txt

# Copy project
COPY . .

# Collect static files
RUN python manage.py collectstatic --noinput --settings=config.settings.prod

# Run with gunicorn
CMD ["gunicorn", "config.wsgi:application", "--bind", "0.0.0.0:8000", "--workers", "3"]

docker-compose.yml (Production)

version: '3.9'

services:
  db:
    image: postgres:15-alpine
    volumes:
      - postgres_data:/var/lib/postgresql/data
    environment:
      POSTGRES_DB: myapp
      POSTGRES_USER: myapp
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}

  redis:
    image: redis:7-alpine

  web:
    build: .
    command: gunicorn config.wsgi:application --bind 0.0.0.0:8000 --workers 3 --timeout 120
    volumes:
      - static_volume:/app/staticfiles
      - media_volume:/app/media
    env_file: .env
    depends_on: [db, redis]

  nginx:
    image: nginx:alpine
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf
      - static_volume:/app/staticfiles
      - media_volume:/app/media
    ports:
      - "80:80"
    depends_on: [web]

  worker:
    build: .
    command: celery -A config worker --loglevel=info
    env_file: .env
    depends_on: [db, redis]

volumes:
  postgres_data:
  static_volume:
  media_volume:

nginx.conf

upstream django {
    server web:8000;
}

server {
    listen 80;
    server_name example.com www.example.com;

    client_max_body_size 20M;

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

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

    location / {
        proxy_pass http://django;
        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;
    }
}

gunicorn Configuration

# gunicorn.conf.py
import multiprocessing

bind = '0.0.0.0:8000'
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = 'sync'      # Use 'uvicorn.workers.UvicornWorker' for ASGI
timeout = 120
keepalive = 5
max_requests = 1000        # Restart workers after N requests (prevent memory leaks)
max_requests_jitter = 50
accesslog = '-'
errorlog = '-'
loglevel = 'warning'

Production Deployment Checklist

  • DEBUG = False
  • SECRET_KEY from environment variable (not hardcoded)
  • ALLOWED_HOSTS configured correctly
  • HTTPS / SSL enabled (SECURE_SSL_REDIRECT = True)
  • Security headers set
  • Static files collected (collectstatic)
  • Database: PostgreSQL (not SQLite)
  • Migrations applied on deploy
  • Error monitoring (Sentry) configured
  • Logging to stdout/stderr (not files, for container compatibility)
  • Health check endpoint available
  • Backups configured for database

Deployment Commands

# Run migrations on deploy
python manage.py migrate --noinput --settings=config.settings.prod

# In Docker entrypoint
#!/bin/bash
python manage.py migrate --noinput
python manage.py collectstatic --noinput
exec "$@"

Related Skills

  • django-performance: Caching and query optimization for production
  • django-celery: Worker and Beat setup in Docker
  • django-debug: Production logging configuration
Weekly Installs
2
First Seen
3 days ago
Installed on
amp2
cline2
opencode2
cursor2
kimi-cli2
codex2