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

502 lines
10 KiB
Markdown

# 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=<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
```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/
```