Deployment Guide
Production Deployment
Prerequisites
Python 3.8+
PostgreSQL 12+ (recommended) or MySQL 5.7+
Redis (optional, for caching)
Web server (Nginx/Apache)
WSGI server (Gunicorn/uWSGI)
Environment Setup
1. Install System Dependencies
# Ubuntu/Debian
sudo apt-get update
sudo apt-get install python3-pip python3-venv postgresql nginx redis-server
# CentOS/RHEL
sudo yum install python3-pip python3-virtualenv postgresql-server nginx redis
2. Create Virtual Environment
python3 -m venv /var/www/myproject/venv
source /var/www/myproject/venv/bin/activate
3. Install Package
pip install wagtail-subscriptions gunicorn psycopg2-binary
Django Configuration
settings/production.py
import os
from .base import *
# Security
DEBUG = False
SECRET_KEY = os.environ['DJANGO_SECRET_KEY']
ALLOWED_HOSTS = ['yourdomain.com', 'www.yourdomain.com']
# HTTPS
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'
# Database
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['DB_HOST'],
'PORT': os.environ.get('DB_PORT', '5432'),
'CONN_MAX_AGE': 600,
}
}
# Cache
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
'LOCATION': os.environ.get('REDIS_URL', 'redis://127.0.0.1:6379/1'),
}
}
# Static files
STATIC_ROOT = '/var/www/myproject/static/'
STATIC_URL = '/static/'
MEDIA_ROOT = '/var/www/myproject/media/'
MEDIA_URL = '/media/'
# Wagtail Subscriptions
WAGTAIL_SUBSCRIPTIONS = {
'PAYMENT_PROCESSORS': {
'stripe': {
'public_key': os.environ['STRIPE_PUBLIC_KEY'],
'secret_key': os.environ['STRIPE_SECRET_KEY'],
'webhook_secret': os.environ['STRIPE_WEBHOOK_SECRET'],
}
},
'DEFAULT_PROCESSOR': 'stripe',
}
# Email
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = os.environ['EMAIL_HOST']
EMAIL_PORT = int(os.environ.get('EMAIL_PORT', 587))
EMAIL_USE_TLS = True
EMAIL_HOST_USER = os.environ['EMAIL_HOST_USER']
EMAIL_HOST_PASSWORD = os.environ['EMAIL_HOST_PASSWORD']
DEFAULT_FROM_EMAIL = os.environ['DEFAULT_FROM_EMAIL']
# Logging
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format': '{levelname} {asctime} {module} {message}',
'style': '{',
},
},
'handlers': {
'file': {
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler',
'filename': '/var/log/myproject/django.log',
'maxBytes': 1024 * 1024 * 15, # 15MB
'backupCount': 10,
'formatter': 'verbose',
},
},
'root': {
'handlers': ['file'],
'level': 'INFO',
},
'loggers': {
'django': {
'handlers': ['file'],
'level': 'INFO',
'propagate': False,
},
'wagtail_subscriptions': {
'handlers': ['file'],
'level': 'INFO',
'propagate': False,
},
},
}
Environment Variables
Create .env file:
# Django
DJANGO_SECRET_KEY=your-secret-key-here
DJANGO_SETTINGS_MODULE=myproject.settings.production
# Database
DB_NAME=myproject_db
DB_USER=myproject_user
DB_PASSWORD=secure-password
DB_HOST=localhost
DB_PORT=5432
# Stripe
STRIPE_PUBLIC_KEY=pk_live_...
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
# Email
EMAIL_HOST=smtp.sendgrid.net
EMAIL_PORT=587
EMAIL_HOST_USER=apikey
EMAIL_HOST_PASSWORD=your-sendgrid-api-key
DEFAULT_FROM_EMAIL=noreply@yourdomain.com
# Redis
REDIS_URL=redis://127.0.0.1:6379/1
Database Setup
# Create database
sudo -u postgres psql
CREATE DATABASE myproject_db;
CREATE USER myproject_user WITH PASSWORD 'secure-password';
ALTER ROLE myproject_user SET client_encoding TO 'utf8';
ALTER ROLE myproject_user SET default_transaction_isolation TO 'read committed';
ALTER ROLE myproject_user SET timezone TO 'UTC';
GRANT ALL PRIVILEGES ON DATABASE myproject_db TO myproject_user;
\q
# Run migrations
python manage.py migrate
python manage.py setup_subscription_permissions
python manage.py collectstatic --noinput
Gunicorn Configuration
Create /etc/systemd/system/myproject.service:
[Unit]
Description=Myproject Gunicorn daemon
After=network.target
[Service]
User=www-data
Group=www-data
WorkingDirectory=/var/www/myproject
EnvironmentFile=/var/www/myproject/.env
ExecStart=/var/www/myproject/venv/bin/gunicorn \
--workers 3 \
--bind unix:/var/www/myproject/myproject.sock \
--timeout 120 \
--access-logfile /var/log/myproject/access.log \
--error-logfile /var/log/myproject/error.log \
myproject.wsgi:application
[Install]
WantedBy=multi-user.target
Start service:
sudo systemctl start myproject
sudo systemctl enable myproject
Nginx Configuration
Create /etc/nginx/sites-available/myproject:
upstream myproject {
server unix:/var/www/myproject/myproject.sock fail_timeout=0;
}
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name yourdomain.com www.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
client_max_body_size 100M;
location /static/ {
alias /var/www/myproject/static/;
expires 30d;
add_header Cache-Control "public, immutable";
}
location /media/ {
alias /var/www/myproject/media/;
expires 30d;
}
location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_pass http://myproject;
}
}
Enable site:
sudo ln -s /etc/nginx/sites-available/myproject /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginx
SSL Certificate (Let’s Encrypt)
sudo apt-get install certbot python3-certbot-nginx
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
Celery Setup (Optional)
For background tasks:
celery.service
[Unit]
Description=Celery Service
After=network.target
[Service]
Type=forking
User=www-data
Group=www-data
EnvironmentFile=/var/www/myproject/.env
WorkingDirectory=/var/www/myproject
ExecStart=/var/www/myproject/venv/bin/celery -A myproject worker \
--loglevel=info \
--logfile=/var/log/myproject/celery.log
[Install]
WantedBy=multi-user.target
celerybeat.service
[Unit]
Description=Celery Beat Service
After=network.target
[Service]
Type=simple
User=www-data
Group=www-data
EnvironmentFile=/var/www/myproject/.env
WorkingDirectory=/var/www/myproject
ExecStart=/var/www/myproject/venv/bin/celery -A myproject beat \
--loglevel=info \
--logfile=/var/log/myproject/celerybeat.log
[Install]
WantedBy=multi-user.target
Start services:
sudo systemctl start celery celerybeat
sudo systemctl enable celery celerybeat
Docker Deployment
Dockerfile
FROM python:3.11-slim
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y \
postgresql-client \
&& rm -rf /var/lib/apt/lists/*
# Install Python dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy project
COPY . .
# Collect static files
RUN python manage.py collectstatic --noinput
EXPOSE 8000
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "3", "myproject.wsgi:application"]
docker-compose.yml
version: '3.8'
services:
db:
image: postgres:14
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
POSTGRES_DB: myproject_db
POSTGRES_USER: myproject_user
POSTGRES_PASSWORD: secure-password
restart: unless-stopped
redis:
image: redis:7-alpine
restart: unless-stopped
web:
build: .
command: gunicorn --bind 0.0.0.0:8000 --workers 3 myproject.wsgi:application
volumes:
- static_volume:/app/static
- media_volume:/app/media
ports:
- "8000:8000"
env_file:
- .env
depends_on:
- db
- redis
restart: unless-stopped
nginx:
image: nginx:alpine
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- static_volume:/app/static
- media_volume:/app/media
ports:
- "80:80"
- "443:443"
depends_on:
- web
restart: unless-stopped
celery:
build: .
command: celery -A myproject worker --loglevel=info
env_file:
- .env
depends_on:
- db
- redis
restart: unless-stopped
celerybeat:
build: .
command: celery -A myproject beat --loglevel=info
env_file:
- .env
depends_on:
- db
- redis
restart: unless-stopped
volumes:
postgres_data:
static_volume:
media_volume:
Deploy:
docker-compose up -d
docker-compose exec web python manage.py migrate
docker-compose exec web python manage.py setup_subscription_permissions
Monitoring
Health Check Endpoint
# views.py
from django.http import JsonResponse
from django.db import connection
def health_check(request):
"""Health check endpoint for monitoring."""
try:
# Check database
with connection.cursor() as cursor:
cursor.execute("SELECT 1")
return JsonResponse({'status': 'healthy'})
except Exception as e:
return JsonResponse({'status': 'unhealthy', 'error': str(e)}, status=500)
Monitoring Tools
Sentry: Error tracking
New Relic: Application performance monitoring
Prometheus + Grafana: Metrics and dashboards
Uptime Robot: Uptime monitoring
Backup Strategy
Database Backup
#!/bin/bash
# backup.sh
BACKUP_DIR="/var/backups/myproject"
DATE=$(date +%Y%m%d_%H%M%S)
# Create backup
pg_dump -U myproject_user myproject_db | gzip > "$BACKUP_DIR/db_$DATE.sql.gz"
# Keep only last 30 days
find $BACKUP_DIR -name "db_*.sql.gz" -mtime +30 -delete
Add to crontab:
0 2 * * * /var/www/myproject/backup.sh
Media Files Backup
# Sync to S3
aws s3 sync /var/www/myproject/media/ s3://your-bucket/media/ --delete
Scaling
Horizontal Scaling
Use load balancer (AWS ELB, Nginx)
Multiple application servers
Shared database and Redis
Centralized media storage (S3, CloudFront)
Database Optimization
Connection pooling (PgBouncer)
Read replicas
Query optimization
Proper indexing
Caching Strategy
Redis for session storage
Cache frequently accessed data
CDN for static files
Database query caching
Security Checklist
[ ] Use HTTPS everywhere
[ ] Set secure cookie flags
[ ] Configure CORS properly
[ ] Enable CSRF protection
[ ] Use strong SECRET_KEY
[ ] Restrict ALLOWED_HOSTS
[ ] Keep dependencies updated
[ ] Regular security audits
[ ] Implement rate limiting
[ ] Monitor for suspicious activity