Skip to main content

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