# 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.

```mermaid
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

```bash
# 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

```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

```yaml
# 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

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

```yaml
# .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

```bash
# 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

```python
# 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

```python
# 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

```python
# 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:

```python
# 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

```python
# Sentry is configured in config/django/production.py
# Enable by setting SENTRY_DSN environment variable
```

## Security Considerations

### Production Security Checklist

- [ ] `DEBUG=False` in production
- [ ] `SECRET_KEY` is unique and secure
- [ ] `ALLOWED_HOSTS` properly 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

```python
# 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

```bash
#!/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

```bash
# 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

1. **Static files not loading**
   - Run `python manage.py collectstatic`
   - Check nginx static file configuration
   - Verify WhiteNoise settings

2. **Database connection errors**
   - Verify DATABASE_URL format
   - Check firewall rules
   - Verify PostGIS extension is installed

3. **CORS errors**
   - Check CORS_ALLOWED_ORIGINS setting
   - Verify CSRF_TRUSTED_ORIGINS

4. **Memory issues**
   - Monitor with `docker stats`
   - Optimize Gunicorn worker count
   - Check for query inefficiencies

### Debug Commands

```bash
# 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.
