Skip to content

Nginx Deployment Configuration

Overview

The AV project now includes an internal Nginx service that serves as a reverse proxy and static file server. This improves performance and follows production best practices.

Architecture

Client Requests
┌─────────────────────────┐
│   Nginx (Port 80)       │
│                         │
│ - Reverse Proxy         │
│ - Static Files Cache    │
│ - Compression (Gzip)    │
└────────┬────────────────┘
         ├─→ /static/  → static_volume (7-day cache)
         ├─→ /media/   → media_volume (1-day cache)
         ├─→ /docs/    → site/ (7-day cache)
         └─→ /*        → Django Web App (8000)

Services

Nginx Container

  • Image: nginx:1.27-alpine
  • Port: 80 (external)
  • Container Name: av_nginx
  • Dependencies: Waits for web service to be healthy

Configuration Files

docker/nginx/Dockerfile

Lightweight alpine-based nginx image with health checks:

FROM nginx:1.27-alpine
COPY nginx.conf /etc/nginx/nginx.conf
HEALTHCHECK --interval=30s --timeout=3s ...

docker/nginx/nginx.conf

Main nginx configuration with: - Worker processes: Auto-detect (scalable) - Client max body size: 100MB - Gzip compression: Enabled for text/JSON/JS assets - Static file caching: 7 days for /static/, 1 day for /media/ - Proxy settings: Passes requests to Django with proper headers - Health endpoint: /health for container health checks

File Serving

Static Files (/static/)

  • Source: static_volume:/app/staticfiles
  • Cache: 7 days
  • Use Cases: CSS, JS, admin assets
  • Served Directly: No Django processing needed

Media Files (/media/)

  • Source: media_volume:/app/media
  • Cache: 1 day
  • Use Cases: User-uploaded files, images
  • Read-Only: Mounted as ro (read-only)

Documentation (/docs/)

  • Source: ./site (MkDocs generated HTML)
  • Cache: 7 days
  • Fallback: Serves index.html for SPA-like behavior

Request Routing

Path Handler Caching Notes
/static/* Nginx 7 days CSS, JS, images
/media/* Nginx 1 day User uploads
/docs/* Nginx 7 days Documentation
/* Django None API endpoints, views

Environment Variables

All existing environment variables work as before. The Django app still runs on port 8000 internally.

For external access: - API: http://localhost/api/ (proxied through nginx) - Docs: http://localhost/docs/ (served by nginx) - Admin: http://localhost/admin/ (proxied through nginx) - Django Dev: http://localhost:8000/ (direct access, bypasses nginx)

Development vs Production

Development (docker compose up)

Port 80 (Nginx) ────→ Static files + Reverse proxy
Port 8000 (Django) ──→ Direct access for debugging

Production Deployment

For production, update the Django service:

# In docker-compose.yml for production
web:
  # ...
  ports: []  # Remove port 8000 exposure
  command: >
    sh -c "
      uv run python manage.py migrate &&
      uv run python manage.py collectstatic --noinput &&
      gunicorn --bind 0.0.0.0:8000 core.wsgi
    "

And update environment:

DEBUG=False
ALLOWED_HOSTS=yourdomain.com,www.yourdomain.com
CORS_ALLOW_ALL_ORIGINS=False
CORS_ALLOWED_ORIGINS=https://yourdomain.com

Nginx Performance Features

Gzip Compression

Enabled for: - Plain text - CSS, XML, JavaScript - JSON - Fonts (TTF, OTF) - SVG images

Reduces bandwidth by ~70-80% for text-based assets.

Caching Headers

/static/  → Cache-Control: public, immutable (7 days)
/media/   → Cache-Control: public (1 day)
/docs/    → Cache-Control: public (7 days)

Connection Optimization

  • sendfile on: Zero-copy file transmission
  • tcp_nopush on: Optimize TCP packet transmission
  • keepalive_timeout 65: Connection reuse

Health Checks

Both services have health checks:

Nginx:

healthcheck:
  test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost/health"]
  interval: 30s
  timeout: 3s
  retries: 3

Django (unchanged):

healthcheck:
  test: ["CMD-SHELL", "pg_isready -U postgres"]
  interval: 10s

Logs

View nginx logs:

docker logs av_nginx

Common log locations in container: - Access logs: /var/log/nginx/access.log - Error logs: /var/log/nginx/error.log

Troubleshooting

Nginx container won't start

docker logs av_nginx
# Check for config syntax errors
docker exec av_nginx nginx -t

Static files not loading

# Verify volumes are mounted
docker inspect av_nginx | grep -A 5 Mounts

# Check file permissions
docker exec av_nginx ls -la /app/staticfiles/

Slow responses

# Check nginx logs for upstream errors
docker logs av_nginx | tail -f

# Monitor nginx performance
docker stats av_nginx

Port 80 already in use

# Change nginx port in docker-compose.yml
ports:
  - "8080:80"  # Maps container:80 to host:8080

# Access at http://localhost:8080/

Advanced Configuration

Add HTTPS (with Let's Encrypt)

Update nginx.conf:

server {
    listen 443 ssl http2;
    ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
    # ... rest of config
}

Add Rate Limiting

limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;

location /api/ {
    limit_req zone=api_limit burst=20 nodelay;
    proxy_pass http://django;
}

Custom Error Pages

error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
    root /app/site;
}

References