mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-24 05: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.
14 KiB
14 KiB
ThrillWiki Deployment Guide
This document outlines deployment strategies, build processes, and infrastructure considerations for the ThrillWiki Django + HTMX application.
Architecture Overview
ThrillWiki is a Django monolith with HTMX for dynamic interactivity. There is no separate frontend build process - templates and static assets are served directly by Django.
graph TB
A[Source Code] --> B[Django Application]
B --> C[Static Files Collection]
C --> D[Docker Container]
D --> E[Production Deployment]
subgraph "Django Application"
B1[Python Dependencies]
B2[Database Migrations]
B3[HTMX Templates]
end
Development Environment
Prerequisites
- Python 3.13+ with UV package manager
- PostgreSQL 14+ with PostGIS extension
- Redis 6+ (for caching and sessions)
Local Development Setup
# Clone repository
git clone <repository-url>
cd thrillwiki
# Install dependencies
cd backend
uv sync --frozen
# Configure environment
cp .env.example .env
# Edit .env with your settings
# Database setup
uv run manage.py migrate
uv run manage.py collectstatic --noinput
# Start development server
uv run manage.py runserver
Build Strategies
1. Containerized Deployment (Recommended)
Multi-stage Dockerfile
# backend/Dockerfile
FROM python:3.13-slim as builder
WORKDIR /app
# Install system dependencies for GeoDjango
RUN apt-get update && apt-get install -y \
binutils libproj-dev gdal-bin libgdal-dev \
libpq-dev gcc \
&& rm -rf /var/lib/apt/lists/*
# Install UV
RUN pip install uv
# Copy dependency files
COPY pyproject.toml uv.lock ./
# Install dependencies
RUN uv sync --frozen --no-dev
FROM python:3.13-slim as runtime
WORKDIR /app
# Install runtime dependencies for GeoDjango
RUN apt-get update && apt-get install -y \
libpq5 gdal-bin libgdal32 libgeos-c1v5 libproj25 \
&& rm -rf /var/lib/apt/lists/*
# Copy virtual environment from builder
COPY --from=builder /app/.venv /app/.venv
ENV PATH="/app/.venv/bin:$PATH"
# Copy application code
COPY . .
# Collect static files
RUN python manage.py collectstatic --noinput
# Create logs directory
RUN mkdir -p logs
EXPOSE 8000
# Run with gunicorn
CMD ["gunicorn", "config.wsgi:application", "--bind", "0.0.0.0:8000", "--workers", "4"]
Docker Compose for Development
# docker-compose.dev.yml
version: '3.8'
services:
db:
image: postgis/postgis:15-3.3
environment:
POSTGRES_DB: thrillwiki
POSTGRES_USER: thrillwiki
POSTGRES_PASSWORD: password
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
redis:
image: redis:7-alpine
ports:
- "6379:6379"
web:
build:
context: ./backend
dockerfile: Dockerfile.dev
ports:
- "8000:8000"
volumes:
- ./backend:/app
- ./shared/media:/app/media
environment:
- DEBUG=1
- DATABASE_URL=postgis://thrillwiki:password@db:5432/thrillwiki
- REDIS_URL=redis://redis:6379/0
depends_on:
- db
- redis
command: python manage.py runserver 0.0.0.0:8000
celery:
build:
context: ./backend
dockerfile: Dockerfile.dev
volumes:
- ./backend:/app
environment:
- DATABASE_URL=postgis://thrillwiki:password@db:5432/thrillwiki
- REDIS_URL=redis://redis:6379/0
depends_on:
- db
- redis
command: celery -A config.celery worker -l info
volumes:
postgres_data:
Docker Compose for Production
# docker-compose.prod.yml
version: '3.8'
services:
db:
image: postgis/postgis:15-3.3
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
restart: unless-stopped
redis:
image: redis:7-alpine
restart: unless-stopped
web:
build:
context: ./backend
dockerfile: Dockerfile
environment:
- DEBUG=0
- DATABASE_URL=${DATABASE_URL}
- REDIS_URL=${REDIS_URL}
- SECRET_KEY=${SECRET_KEY}
- ALLOWED_HOSTS=${ALLOWED_HOSTS}
volumes:
- ./shared/media:/app/media
- static_files:/app/staticfiles
depends_on:
- db
- redis
restart: unless-stopped
celery:
build:
context: ./backend
dockerfile: Dockerfile
environment:
- DATABASE_URL=${DATABASE_URL}
- REDIS_URL=${REDIS_URL}
- SECRET_KEY=${SECRET_KEY}
depends_on:
- db
- redis
command: celery -A config.celery worker -l info
restart: unless-stopped
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
- ./nginx/ssl:/etc/nginx/ssl
- static_files:/usr/share/nginx/html/static
- ./shared/media:/usr/share/nginx/html/media
depends_on:
- web
restart: unless-stopped
volumes:
postgres_data:
static_files:
Nginx Configuration
# nginx/nginx.conf
upstream django {
server web:8000;
}
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/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
# Security headers
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Static files
location /static/ {
alias /usr/share/nginx/html/static/;
expires 1y;
add_header Cache-Control "public, immutable";
}
# Media files
location /media/ {
alias /usr/share/nginx/html/media/;
expires 1M;
add_header Cache-Control "public";
}
# Django application
location / {
proxy_pass http://django;
proxy_set_header Host $http_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;
# HTMX considerations
proxy_set_header HX-Request $http_hx_request;
proxy_set_header HX-Current-URL $http_hx_current_url;
}
# Health check endpoint
location /api/v1/health/simple/ {
proxy_pass http://django;
proxy_set_header Host $http_host;
access_log off;
}
}
CI/CD Pipeline
GitHub Actions Workflow
# .github/workflows/deploy.yml
name: Deploy ThrillWiki
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgis/postgis:15-3.3
env:
POSTGRES_PASSWORD: postgres
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
redis:
image: redis:7-alpine
ports:
- 6379:6379
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.13'
- name: Install UV
run: pip install uv
- name: Cache dependencies
uses: actions/cache@v4
with:
path: ~/.cache/uv
key: ${{ runner.os }}-uv-${{ hashFiles('backend/uv.lock') }}
- name: Install dependencies
run: |
cd backend
uv sync --frozen
- name: Run tests
run: |
cd backend
uv run manage.py test
env:
DATABASE_URL: postgis://postgres:postgres@localhost:5432/postgres
REDIS_URL: redis://localhost:6379/0
SECRET_KEY: test-secret-key
DEBUG: "1"
- name: Run linting
run: |
cd backend
uv run ruff check .
uv run black --check .
build:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Build Docker image
run: |
docker build -t thrillwiki-web ./backend
- name: Push to registry
run: |
# Push to your container registry
# docker push your-registry/thrillwiki-web:${{ github.sha }}
deploy:
needs: build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- name: Deploy to production
run: |
# Deploy using your preferred method
# SSH, Kubernetes, AWS ECS, etc.
Environment Configuration
Required Environment Variables
# Django Settings
DEBUG=0
SECRET_KEY=your-production-secret-key
ALLOWED_HOSTS=yourdomain.com,www.yourdomain.com
CSRF_TRUSTED_ORIGINS=https://yourdomain.com,https://www.yourdomain.com
DJANGO_SETTINGS_MODULE=config.django.production
# Database
DATABASE_URL=postgis://user:password@host:port/database
# Redis
REDIS_URL=redis://host:port/0
# Email
EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend
EMAIL_HOST=smtp.yourmailprovider.com
EMAIL_PORT=587
EMAIL_USE_TLS=True
EMAIL_HOST_USER=your-email@yourdomain.com
EMAIL_HOST_PASSWORD=your-email-password
# Cloudflare Images
CLOUDFLARE_IMAGES_ACCOUNT_ID=your-account-id
CLOUDFLARE_IMAGES_API_TOKEN=your-api-token
CLOUDFLARE_IMAGES_ACCOUNT_HASH=your-account-hash
# Sentry (optional)
SENTRY_DSN=your-sentry-dsn
SENTRY_ENVIRONMENT=production
Performance Optimization
Database Optimization
# backend/config/django/production.py
DATABASES = {
'default': {
'ENGINE': 'django.contrib.gis.db.backends.postgis',
'CONN_MAX_AGE': 60, # Keep connections alive for 60 seconds
'OPTIONS': {
'connect_timeout': 10,
'options': '-c statement_timeout=30000', # 30 second query timeout
}
}
}
Redis Caching
# Caching configuration is in config/django/production.py
# Multiple cache backends for different purposes:
# - default: General caching
# - sessions: Session storage
# - api: API response caching
Static Files with WhiteNoise
# backend/config/django/production.py
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
Monitoring and Logging
Health Check Endpoints
| Endpoint | Purpose | Use Case |
|---|---|---|
/api/v1/health/ |
Comprehensive health check | Monitoring dashboards |
/api/v1/health/simple/ |
Simple OK/ERROR | Load balancer health checks |
/api/v1/health/performance/ |
Performance metrics | Debug mode only |
Logging Configuration
Production logging uses JSON format for log aggregation:
# backend/config/django/production.py
LOGGING = {
'handlers': {
'console': {
'class': 'logging.StreamHandler',
'formatter': 'json',
},
'file': {
'class': 'logging.handlers.RotatingFileHandler',
'filename': 'logs/django.log',
'maxBytes': 1024 * 1024 * 15, # 15MB
'backupCount': 10,
'formatter': 'json',
},
},
}
Sentry Integration
# Sentry is configured in config/django/production.py
# Enable by setting SENTRY_DSN environment variable
Security Considerations
Production Security Checklist
DEBUG=Falsein productionSECRET_KEYis unique and secureALLOWED_HOSTSproperly configured- HTTPS enforced with SSL certificates
- Security headers configured (HSTS, CSP, etc.)
- Database credentials secured
- Redis password configured (if exposed)
- CORS properly configured
- Rate limiting enabled
- File upload validation
- SQL injection protection (Django ORM)
- XSS protection enabled
- CSRF protection active
Security Headers
# backend/config/django/production.py
SECURE_SSL_REDIRECT = True
SECURE_HSTS_SECONDS = 31536000 # 1 year
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
X_FRAME_OPTIONS = 'DENY'
SECURE_CONTENT_TYPE_NOSNIFF = True
Backup and Recovery
Database Backup Strategy
#!/bin/bash
# Automated backup script
pg_dump $DATABASE_URL | gzip > backup_$(date +%Y%m%d_%H%M%S).sql.gz
aws s3 cp backup_*.sql.gz s3://your-backup-bucket/database/
Media Files Backup
# Sync media files to S3
aws s3 sync ./shared/media/ s3://your-media-bucket/media/ --delete
Scaling Strategies
Horizontal Scaling
- Use load balancer (nginx, AWS ALB, etc.)
- Database read replicas for read-heavy workloads
- CDN for static assets (Cloudflare, CloudFront)
- Redis cluster for session/cache scaling
- Multiple Gunicorn workers per container
Vertical Scaling
- Database connection pooling (pgBouncer)
- Query optimization with select_related/prefetch_related
- Memory usage optimization
- Background task offloading to Celery
Troubleshooting Guide
Common Issues
-
Static files not loading
- Run
python manage.py collectstatic - Check nginx static file configuration
- Verify WhiteNoise settings
- Run
-
Database connection errors
- Verify DATABASE_URL format
- Check firewall rules
- Verify PostGIS extension is installed
-
CORS errors
- Check CORS_ALLOWED_ORIGINS setting
- Verify CSRF_TRUSTED_ORIGINS
-
Memory issues
- Monitor with
docker stats - Optimize Gunicorn worker count
- Check for query inefficiencies
- Monitor with
Debug Commands
# Check Django configuration
cd backend
uv run manage.py check --deploy
# Database shell
uv run manage.py dbshell
# Django shell
uv run manage.py shell
# Validate settings
uv run manage.py validate_settings
This deployment guide provides a comprehensive approach to deploying the ThrillWiki Django + HTMX application while maintaining security, performance, and scalability.