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_KEYfrom environment variable (not hardcoded) -
ALLOWED_HOSTSconfigured 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
Repository
ristemingov/dja…de-setupFirst Seen
3 days ago
Security Audits
Installed on
amp2
cline2
opencode2
cursor2
kimi-cli2
codex2