mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-24 21:31:09 -05:00
- 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.
502 lines
10 KiB
Markdown
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/
|
|
```
|