Files
thrillwiki_django_no_react/docs/configuration/deployment.md
pacnpal edcd8f2076 Add secret management guide, client-side performance monitoring, and search accessibility enhancements
- Introduced a comprehensive Secret Management Guide detailing best practices, secret classification, development setup, production management, rotation procedures, and emergency protocols.
- Implemented a client-side performance monitoring script to track various metrics including page load performance, paint metrics, layout shifts, and memory usage.
- Enhanced search accessibility with keyboard navigation support for search results, ensuring compliance with WCAG standards and improving user experience.
2025-12-23 16:41:42 -05:00

10 KiB

Deployment Guide

This guide covers deploying ThrillWiki to production environments, including configuration, security, and monitoring setup.

Pre-Deployment Checklist

Configuration

  • All required environment variables set
  • DEBUG=False
  • Strong SECRET_KEY (50+ characters, randomly generated)
  • ALLOWED_HOSTS properly configured
  • CSRF_TRUSTED_ORIGINS set
  • Database URL points to production database
  • Redis URL configured for caching

Security

  • SECURE_SSL_REDIRECT=True
  • SESSION_COOKIE_SECURE=True
  • CSRF_COOKIE_SECURE=True
  • SECURE_HSTS_SECONDS set (31536000 recommended)
  • SECURE_HSTS_INCLUDE_SUBDOMAINS=True
  • No debug tools enabled
  • Turnstile keys configured

Validation

Run the configuration validator:

python manage.py validate_settings --strict
python manage.py check --deploy

Environment Configuration

Required Environment Variables

# Core Django
SECRET_KEY=<generate-secure-key>
DEBUG=False
DJANGO_SETTINGS_MODULE=config.django.production
ALLOWED_HOSTS=thrillwiki.com,www.thrillwiki.com
CSRF_TRUSTED_ORIGINS=https://thrillwiki.com,https://www.thrillwiki.com

# Database
DATABASE_URL=postgis://user:pass@db-host:5432/thrillwiki

# Cache
REDIS_URL=redis://:password@redis-host:6379/1

# Email
EMAIL_BACKEND=django_forwardemail.backends.ForwardEmailBackend
FORWARD_EMAIL_API_KEY=your-api-key
FORWARD_EMAIL_DOMAIN=thrillwiki.com

# Security
TURNSTILE_SITE_KEY=your-site-key
TURNSTILE_SECRET_KEY=your-secret-key
SECURE_SSL_REDIRECT=True
SESSION_COOKIE_SECURE=True
CSRF_COOKIE_SECURE=True

# Cloudflare Images
CLOUDFLARE_IMAGES_ACCOUNT_ID=your-account-id
CLOUDFLARE_IMAGES_API_TOKEN=your-api-token
CLOUDFLARE_IMAGES_ACCOUNT_HASH=your-account-hash

# Road Trip Service
ROADTRIP_USER_AGENT=ThrillWiki/1.0 (https://thrillwiki.com)

# Frontend
FRONTEND_DOMAIN=https://thrillwiki.com

Optional Production Settings

# Monitoring
SENTRY_DSN=https://xxx@xxx.ingest.sentry.io/xxx
SENTRY_ENVIRONMENT=production
SENTRY_TRACES_SAMPLE_RATE=0.1

# Secret rotation
SECRET_ROTATION_ENABLED=True
SECRET_KEY_VERSION=1

# Performance tuning
DATABASE_CONN_MAX_AGE=600
REDIS_MAX_CONNECTIONS=100

Deployment Platforms

Docker Deployment

Create a production Dockerfile:

FROM python:3.11-slim

WORKDIR /app

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

# Install Python dependencies
COPY backend/pyproject.toml backend/uv.lock ./
RUN pip install uv && uv sync --frozen

# Copy application
COPY backend/ .

# Collect static files
RUN python manage.py collectstatic --noinput

# Run with Gunicorn
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "4", "thrillwiki.wsgi:application"]

Docker Compose for production:

version: '3.8'

services:
  web:
    build: .
    environment:
      - DJANGO_SETTINGS_MODULE=config.django.production
    env_file:
      - .env
    ports:
      - "8000:8000"
    depends_on:
      - db
      - redis

  db:
    image: postgis/postgis:14-3.3
    volumes:
      - postgres_data:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=thrillwiki
      - POSTGRES_USER=thrillwiki
      - POSTGRES_PASSWORD=${DB_PASSWORD}

  redis:
    image: redis:7-alpine
    command: redis-server --requirepass ${REDIS_PASSWORD}
    volumes:
      - redis_data:/data

  celery:
    build: .
    command: celery -A config.celery worker -l info
    env_file:
      - .env
    depends_on:
      - db
      - redis

  celery-beat:
    build: .
    command: celery -A config.celery beat -l info
    env_file:
      - .env
    depends_on:
      - db
      - redis

volumes:
  postgres_data:
  redis_data:

Heroku Deployment

# Create app
heroku create thrillwiki

# Add buildpacks
heroku buildpacks:add heroku/python
heroku buildpacks:add https://github.com/heroku/heroku-geo-buildpack.git

# Add add-ons
heroku addons:create heroku-postgresql:standard-0
heroku addons:create heroku-redis:premium-0

# Set environment variables
heroku config:set DJANGO_SETTINGS_MODULE=config.django.production
heroku config:set SECRET_KEY=$(python -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())")
heroku config:set DEBUG=False
# ... set other variables

# Deploy
git push heroku main

# Run migrations
heroku run python manage.py migrate

AWS ECS Deployment

Task definition example:

{
  "family": "thrillwiki",
  "containerDefinitions": [
    {
      "name": "web",
      "image": "your-ecr-repo/thrillwiki:latest",
      "portMappings": [
        {
          "containerPort": 8000,
          "protocol": "tcp"
        }
      ],
      "environment": [
        {
          "name": "DJANGO_SETTINGS_MODULE",
          "value": "config.django.production"
        }
      ],
      "secrets": [
        {
          "name": "SECRET_KEY",
          "valueFrom": "arn:aws:secretsmanager:region:account:secret:thrillwiki/SECRET_KEY"
        },
        {
          "name": "DATABASE_URL",
          "valueFrom": "arn:aws:secretsmanager:region:account:secret:thrillwiki/DATABASE_URL"
        }
      ]
    }
  ]
}

Database Setup

PostgreSQL with PostGIS

-- Create database
CREATE DATABASE thrillwiki;

-- Enable PostGIS
\c thrillwiki
CREATE EXTENSION postgis;
CREATE EXTENSION postgis_topology;

-- Create user
CREATE USER thrillwiki_app WITH ENCRYPTED PASSWORD 'secure-password';
GRANT ALL PRIVILEGES ON DATABASE thrillwiki TO thrillwiki_app;

Run Migrations

python manage.py migrate
python manage.py createsuperuser

Static Files

Collect Static Files

python manage.py collectstatic --noinput

WhiteNoise Configuration

WhiteNoise is configured for production static file serving. No additional web server configuration needed for static files.

CDN Integration (Optional)

For CDN, update STATIC_URL:

STATIC_URL=https://cdn.thrillwiki.com/static/

Monitoring Setup

Sentry Integration

SENTRY_DSN=https://xxx@xxx.ingest.sentry.io/xxx
SENTRY_ENVIRONMENT=production
SENTRY_TRACES_SAMPLE_RATE=0.1

Health Checks

Health check endpoint: /health/

# Test health check
curl https://thrillwiki.com/health/

Configure load balancer health checks to use this endpoint.

Logging

Production logging outputs JSON to stdout (suitable for log aggregation):

{
  "levelname": "INFO",
  "asctime": "2024-01-15 10:30:00,000",
  "module": "views",
  "message": "Request processed"
}

Performance Tuning

Database Connection Pooling

DATABASE_CONN_MAX_AGE=600  # 10 minutes
DATABASE_CONNECT_TIMEOUT=10
DATABASE_STATEMENT_TIMEOUT=30000

Redis Connection Pooling

REDIS_MAX_CONNECTIONS=100
REDIS_CONNECTION_TIMEOUT=20

Gunicorn Configuration

# gunicorn.conf.py
workers = 4  # (2 * CPU cores) + 1
worker_class = "gevent"
worker_connections = 1000
timeout = 30
keepalive = 5
max_requests = 1000
max_requests_jitter = 50

Celery Workers

Start Workers

# Main worker
celery -A config.celery worker -l info

# Beat scheduler
celery -A config.celery beat -l info

# With specific queues
celery -A config.celery worker -l info -Q default,trending,analytics,cache

Supervisor Configuration

[program:celery]
command=/app/.venv/bin/celery -A config.celery worker -l info
directory=/app
user=www-data
numprocs=1
autostart=true
autorestart=true
stdout_logfile=/var/log/celery/worker.log
stderr_logfile=/var/log/celery/worker-error.log

[program:celery-beat]
command=/app/.venv/bin/celery -A config.celery beat -l info
directory=/app
user=www-data
numprocs=1
autostart=true
autorestart=true
stdout_logfile=/var/log/celery/beat.log
stderr_logfile=/var/log/celery/beat-error.log

SSL/TLS Configuration

Nginx Configuration

server {
    listen 443 ssl http2;
    server_name thrillwiki.com www.thrillwiki.com;

    ssl_certificate /etc/letsencrypt/live/thrillwiki.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/thrillwiki.com/privkey.pem;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;

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

server {
    listen 80;
    server_name thrillwiki.com www.thrillwiki.com;
    return 301 https://$server_name$request_uri;
}

Post-Deployment Verification

Functional Tests

# API health
curl -s https://thrillwiki.com/api/health/ | jq

# Static files
curl -I https://thrillwiki.com/static/css/tailwind.css

# API endpoint
curl -s https://thrillwiki.com/api/v1/parks/ | jq

Security Headers

# Check security headers
curl -I https://thrillwiki.com/ | grep -E "(Strict-Transport|X-Frame|X-Content|Content-Security)"

Performance

# Response time
time curl -s https://thrillwiki.com/api/v1/parks/ > /dev/null

Rollback Procedure

If deployment fails:

  1. Immediate rollback:

    # Docker
    docker-compose down
    docker-compose -f docker-compose.previous.yml up -d
    
    # Heroku
    heroku rollback
    
    # Kubernetes
    kubectl rollout undo deployment/thrillwiki
    
  2. Database rollback (if needed):

    python manage.py migrate app_name migration_number
    
  3. Investigate and fix:

    • Check error logs
    • Review configuration differences
    • Test in staging environment

Maintenance

Regular Tasks

  • Daily: Review error logs, check health endpoints
  • Weekly: Review performance metrics, check disk usage
  • Monthly: Rotate secrets, review security updates
  • Quarterly: Full security audit, dependency updates

Backup Procedures

# Database backup
pg_dump thrillwiki > backup_$(date +%Y%m%d).sql

# Media files backup
aws s3 sync /app/media s3://thrillwiki-backups/media/