Deployment
Production Settings
Environment Configuration
# settings/production.py
import os
from .base import *
DEBUG = False
ALLOWED_HOSTS = ['yourdomain.com', 'www.yourdomain.com']
# Database
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.environ.get('DB_NAME'),
'USER': os.environ.get('DB_USER'),
'PASSWORD': os.environ.get('DB_PASSWORD'),
'HOST': os.environ.get('DB_HOST', 'localhost'),
'PORT': os.environ.get('DB_PORT', '5432'),
}
}
# Security
SECRET_KEY = os.environ.get('SECRET_KEY')
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
X_FRAME_OPTIONS = 'DENY'
# HSTS
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
# Static files
STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'staticfiles'
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
# Media files
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'
# Logging
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'file': {
'level': 'INFO',
'class': 'logging.FileHandler',
'filename': '/var/log/django/django.log',
},
},
'loggers': {
'django': {
'handlers': ['file'],
'level': 'INFO',
'propagate': True,
},
},
}
Environment Variables
# .env (production)
SECRET_KEY=your-super-secret-key-here
DEBUG=False
DB_NAME=myproject_prod
DB_USER=myproject_user
DB_PASSWORD=secure_password
DB_HOST=localhost
DB_PORT=5432
ALLOWED_HOSTS=yourdomain.com,www.yourdomain.com
Docker Deployment
Dockerfile
FROM python:3.11-slim
# Set environment variables
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
# Set work directory
WORKDIR /app
# Install system dependencies
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
postgresql-client \
build-essential \
libpq-dev \
&& rm -rf /var/lib/apt/lists/*
# Install Python dependencies
COPY requirements.txt /app/
RUN pip install --no-cache-dir -r requirements.txt
# Copy project
COPY . /app/
# Collect static files
RUN python manage.py collectstatic --noinput
# Create non-root user
RUN adduser --disabled-password --gecos '' appuser
RUN chown -R appuser:appuser /app
USER appuser
# Expose port
EXPOSE 8000
# Run gunicorn
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "myproject.wsgi:application"]
docker-compose.yml
version: '3.8'
services:
db:
image: postgres:13
volumes:
- postgres_data:/var/lib/postgresql/data/
environment:
- POSTGRES_DB=myproject
- POSTGRES_USER=myproject_user
- POSTGRES_PASSWORD=secure_password
ports:
- '5432:5432'
redis:
image: redis:7-alpine
ports:
- '6379:6379'
web:
build: .
command: gunicorn myproject.wsgi:application --bind 0.0.0.0:8000
volumes:
- .:/app
- static_volume:/app/staticfiles
- media_volume:/app/media
ports:
- '8000:8000'
depends_on:
- db
- redis
environment:
- DEBUG=False
- SECRET_KEY=your-secret-key
- DB_NAME=myproject
- DB_USER=myproject_user
- DB_PASSWORD=secure_password
- DB_HOST=db
- DB_PORT=5432
- REDIS_URL=redis://redis:6379/0
nginx:
image: nginx:alpine
ports:
- '80:80'
- '443:443'
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- static_volume:/app/staticfiles
- media_volume:/app/media
- ./ssl:/etc/nginx/ssl
depends_on:
- web
volumes:
postgres_data:
static_volume:
media_volume:
Nginx Configuration
# nginx.conf
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
upstream django {
server web:8000;
}
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
server_name yourdomain.com www.yourdomain.com;
ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;
client_max_body_size 100M;
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;
}
location /static/ {
alias /app/staticfiles/;
expires 1y;
add_header Cache-Control "public, immutable";
}
location /media/ {
alias /app/media/;
expires 1y;
add_header Cache-Control "public";
}
}
}
Server Configuration
Gunicorn Configuration
# gunicorn.conf.py
bind = "0.0.0.0:8000"
workers = 4
worker_class = "sync"
worker_connections = 1000
max_requests = 1000
max_requests_jitter = 50
timeout = 30
keepalive = 2
user = "appuser"
group = "appuser"
tmp_upload_dir = None
errorlog = "/var/log/gunicorn/error.log"
accesslog = "/var/log/gunicorn/access.log"
access_log_format = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"'
Systemd Service
# /etc/systemd/system/myproject.service
[Unit]
Description=MyProject Django App
After=network.target
[Service]
Type=exec
User=appuser
Group=appuser
WorkingDirectory=/opt/myproject
ExecStart=/opt/myproject/venv/bin/gunicorn --config gunicorn.conf.py myproject.wsgi:application
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=5
PrivateTmp=true
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
Start Services
# Enable and start services
sudo systemctl enable myproject
sudo systemctl start myproject
sudo systemctl status myproject
# Nginx
sudo systemctl enable nginx
sudo systemctl start nginx
sudo systemctl status nginx
Database Configuration
PostgreSQL Setup
# Install PostgreSQL
sudo apt-get update
sudo apt-get install postgresql postgresql-contrib
# Create database and user
sudo -u postgres psql
CREATE DATABASE myproject;
CREATE USER myproject_user WITH PASSWORD 'secure_password';
GRANT ALL PRIVILEGES ON DATABASE myproject TO myproject_user;
\q
# Configure PostgreSQL
sudo nano /etc/postgresql/13/main/pg_hba.conf
# Add line: local myproject myproject_user md5
sudo systemctl restart postgresql
Database Migrations
# Run migrations
python manage.py migrate
# Create superuser
python manage.py createsuperuser
# Load initial data
python manage.py loaddata initial_data.json
Static Files
WhiteNoise Configuration
# settings.py
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
# ... other middleware
]
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
Collect Static Files
# Collect static files
python manage.py collectstatic --noinput
# Compress static files (if using compressor)
python manage.py compress
SSL/TLS Certificate
Let's Encrypt with Certbot
# Install certbot
sudo apt-get install certbot python3-certbot-nginx
# Obtain certificate
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
# Auto-renewal
sudo crontab -e
# Add line: 0 12 * * * /usr/bin/certbot renew --quiet
Manual SSL Configuration
# nginx SSL configuration
server {
listen 443 ssl http2;
server_name yourdomain.com;
ssl_certificate /etc/ssl/certs/yourdomain.com.pem;
ssl_certificate_key /etc/ssl/private/yourdomain.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# ... rest of configuration
}
Monitoring and Logging
Logging Configuration
# settings/production.py
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}',
'style': '{',
},
},
'handlers': {
'file': {
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler',
'filename': '/var/log/django/django.log',
'maxBytes': 1024*1024*5, # 5 MB
'backupCount': 5,
'formatter': 'verbose',
},
'error_file': {
'level': 'ERROR',
'class': 'logging.handlers.RotatingFileHandler',
'filename': '/var/log/django/error.log',
'maxBytes': 1024*1024*5,
'backupCount': 5,
'formatter': 'verbose',
},
},
'loggers': {
'django': {
'handlers': ['file'],
'level': 'INFO',
'propagate': True,
},
'myapp': {
'handlers': ['file', 'error_file'],
'level': 'INFO',
'propagate': False,
},
},
}
Health Check Endpoint
# views.py
from django.http import JsonResponse
from django.db import connection
def health_check(request):
try:
# Check database connection
with connection.cursor() as cursor:
cursor.execute("SELECT 1")
return JsonResponse({
'status': 'healthy',
'database': 'connected'
})
except Exception as e:
return JsonResponse({
'status': 'unhealthy',
'error': str(e)
}, status=500)
Backup and Recovery
Database Backup
#!/bin/bash
# backup.sh
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/opt/backups"
DB_NAME="myproject"
DB_USER="myproject_user"
# Create backup directory
mkdir -p $BACKUP_DIR
# Backup database
pg_dump -U $DB_USER -h localhost $DB_NAME > $BACKUP_DIR/db_backup_$DATE.sql
# Backup media files
tar -czf $BACKUP_DIR/media_backup_$DATE.tar.gz /opt/myproject/media/
# Remove old backups (keep last 30 days)
find $BACKUP_DIR -name "*.sql" -mtime +30 -delete
find $BACKUP_DIR -name "*.tar.gz" -mtime +30 -delete
Automated Backup
# Add to crontab
crontab -e
# Daily backup at 2 AM
0 2 * * * /opt/scripts/backup.sh
# Weekly backup to remote storage
0 3 * * 0 /opt/scripts/backup_to_s3.sh
Performance Optimization
Database Optimization
# settings.py
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'myproject',
'USER': 'myproject_user',
'PASSWORD': 'secure_password',
'HOST': 'localhost',
'PORT': '5432',
'OPTIONS': {
'MAX_CONNS': 20,
'CONN_MAX_AGE': 600,
},
}
}
# Connection pooling
DATABASES['default']['ENGINE'] = 'django_postgrespool'
Caching
# Redis caching
CACHES = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379/1',
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
}
}
}
# Session cache
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
SESSION_CACHE_ALIAS = 'default'
Security Hardening
Security Headers
# settings.py
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
X_FRAME_OPTIONS = 'DENY'
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
# CSP
CSP_DEFAULT_SRC = ("'self'",)
CSP_SCRIPT_SRC = ("'self'", "'unsafe-inline'", "https://cdnjs.cloudflare.com")
CSP_STYLE_SRC = ("'self'", "'unsafe-inline'", "https://fonts.googleapis.com")
Rate Limiting
# Install django-ratelimit
pip install django-ratelimit
# views.py
from django_ratelimit.decorators import ratelimit
@ratelimit(key='ip', rate='5/m', method='POST')
def login_view(request):
# Login logic
pass
Deployment Checklist
Pre-deployment
- Set DEBUG = False
- Configure ALLOWED_HOSTS
- Set up secure SECRET_KEY
- Configure production database
- Set up static files serving
- Configure media files handling
- Set up SSL/TLS certificates
- Configure logging
- Set up monitoring
- Configure backup strategy
Post-deployment
- Run migrations
- Collect static files
- Create superuser
- Test all functionality
- Monitor logs
- Set up automated backups
- Configure monitoring alerts
- Test backup restoration
- Document deployment process
Maintenance
- Regular security updates
- Database maintenance
- Log rotation
- Performance monitoring
- Backup verification
- SSL certificate renewal
- Dependency updates
- Security audits