# 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: ```bash python manage.py validate_settings --strict python manage.py check --deploy ``` ## Environment Configuration ### Required Environment Variables ```bash # Core Django SECRET_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 ```bash # 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: ```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: ```yaml 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 ```bash # 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: ```json { "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 ```sql -- 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 ```bash python manage.py migrate python manage.py createsuperuser ``` ## Static Files ### Collect Static Files ```bash 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`: ```bash STATIC_URL=https://cdn.thrillwiki.com/static/ ``` ## Monitoring Setup ### Sentry Integration ```bash SENTRY_DSN=https://xxx@xxx.ingest.sentry.io/xxx SENTRY_ENVIRONMENT=production SENTRY_TRACES_SAMPLE_RATE=0.1 ``` ### Health Checks Health check endpoint: `/health/` ```bash # 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): ```json { "levelname": "INFO", "asctime": "2024-01-15 10:30:00,000", "module": "views", "message": "Request processed" } ``` ## Performance Tuning ### Database Connection Pooling ```bash DATABASE_CONN_MAX_AGE=600 # 10 minutes DATABASE_CONNECT_TIMEOUT=10 DATABASE_STATEMENT_TIMEOUT=30000 ``` ### Redis Connection Pooling ```bash REDIS_MAX_CONNECTIONS=100 REDIS_CONNECTION_TIMEOUT=20 ``` ### Gunicorn Configuration ```bash # 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 ```bash # 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 ```ini [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 ```nginx 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 ```bash # 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 ```bash # Check security headers curl -I https://thrillwiki.com/ | grep -E "(Strict-Transport|X-Frame|X-Content|Content-Security)" ``` ### Performance ```bash # Response time time curl -s https://thrillwiki.com/api/v1/parks/ > /dev/null ``` ## Rollback Procedure If deployment fails: 1. **Immediate rollback:** ```bash # 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):** ```bash 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 ```bash # Database backup pg_dump thrillwiki > backup_$(date +%Y%m%d).sql # Media files backup aws s3 sync /app/media s3://thrillwiki-backups/media/ ```