Skip to content

Caddy Development Setup Guide

This guide provides sample Caddy configurations for development environments as an alternative to nginx. Caddy offers automatic HTTPS, simpler configuration syntax, and built-in reverse proxy capabilities.

Overview

While the tracker system uses AWS ELB in production, Caddy is an excellent choice for development environments because of:

  • Automatic HTTPS: Automatic SSL certificate generation and renewal
  • Simple Configuration: JSON or Caddyfile syntax that's easier to read and write
  • Built-in Reverse Proxy: No need for complex proxy configurations
  • Hot Reloading: Configuration changes without restart
  • Modern Features: HTTP/2, HTTP/3, and WebSocket support out of the box

Basic Development Configuration

Simple Caddyfile

For basic development setups, here's a minimal Caddyfile:

# Caddyfile for basic development
tracker.local {
    # API Backend (FastAPI)
    handle /api/* {
        reverse_proxy localhost:8100
    }

    # Static files from API
    handle /static/* {
        reverse_proxy localhost:8100
    }

    # Admin Panel
    handle /admin/* {
        reverse_proxy localhost:3000
    }

    # User Frontend (default)
    handle /* {
        reverse_proxy localhost:3100
    }
}

Docker Compose Integration

For Docker-based development using service names:

# Caddyfile for Docker Compose
tracker.local {
    # API Backend
    handle /api/* {
        reverse_proxy api:8100
    }

    # Static files
    handle /static/* {
        reverse_proxy api:8100
    }

    # Admin Panel
    handle /admin/* {
        reverse_proxy tracker-admin-dev:3000
    }

    # User Frontend
    handle /* {
        reverse_proxy tracker-frontend-dev:3100
    }
}

Advanced Configuration

Multiple Domains with Automatic HTTPS

# Main application
tracker.local {
    handle /api/* {
        reverse_proxy api:8100 {
            header_up Host {http.request.host}
            header_up X-Forwarded-Proto {http.request.scheme}
        }
    }

    handle /static/* {
        reverse_proxy api:8100 {
            header_down Cache-Control "public, max-age=604800"
        }
    }

    handle /admin/* {
        reverse_proxy tracker-admin-dev:3000
    }

    handle /* {
        reverse_proxy tracker-frontend-dev:3100
    }
}

# Admin-only subdomain
admin.tracker.local {
    handle /api/* {
        reverse_proxy api:8100
    }

    handle /static/* {
        reverse_proxy api:8100
    }

    handle /* {
        reverse_proxy tracker-admin-dev:3000
    }
}

# API-only subdomain
api.tracker.local {
    reverse_proxy api:8100 {
        header_up Host {http.request.host}
        header_up X-Forwarded-Proto {http.request.scheme}
    }
}

Enhanced Security and Performance

tracker.local {
    # Security headers
    header {
        # Security
        X-Frame-Options "SAMEORIGIN"
        X-XSS-Protection "1; mode=block"
        X-Content-Type-Options "nosniff"
        Referrer-Policy "strict-origin-when-cross-origin"
        Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https://{host};"

        # Remove server info
        -Server
    }

    # Enable compression
    encode gzip zstd

    # API with CORS support
    handle /api/* {
        reverse_proxy api:8100 {
            header_up Host {http.request.host}
            header_up X-Real-IP {http.request.remote_host}
            header_up X-Forwarded-For {http.request.remote_host}
            header_up X-Forwarded-Proto {http.request.scheme}
        }

        # CORS headers
        @cors_preflight method OPTIONS
        handle @cors_preflight {
            header Access-Control-Allow-Origin "*"
            header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
            header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization"
            header Access-Control-Max-Age "1728000"
            respond "" 204
        }
    }

    # Static files with caching
    handle /static/* {
        reverse_proxy api:8100 {
            header_down Cache-Control "public, max-age=604800"
            header_down Expires "7d"
        }
    }

    # Admin Panel with WebSocket support
    handle /admin/* {
        reverse_proxy tracker-admin-dev:3000
    }

    # User Frontend with WebSocket support
    handle /* {
        reverse_proxy tracker-frontend-dev:3100
    }
}

Service-Specific Configurations

API Gateway Pattern

# API Gateway with microservices routing
api.tracker.local {
    # Main API
    handle /api/v1/* {
        reverse_proxy api:8100 {
            header_up Host {http.request.host}
        }
    }

    # Geocoding service
    handle /api/geocoding/* {
        reverse_proxy geocoding-service:8002 {
            header_up Host {http.request.host}
        }
    }

    # Tracker fetcher service
    handle /api/fetcher/* {
        reverse_proxy tracker-fetcher:8001 {
            header_up Host {http.request.host}
        }
    }

    # Health check endpoint
    handle /health {
        respond "healthy" 200 {
            header Content-Type "text/plain"
        }
    }
}

Load Balancing

tracker.local {
    # Load balance across multiple API instances
    handle /api/* {
        reverse_proxy {
            to api-1:8100
            to api-2:8100
            to api-3:8100

            health_uri /health
            health_interval 30s
            health_timeout 5s
        }
    }

    # Static files
    handle /static/* {
        reverse_proxy api-1:8100 api-2:8100 api-3:8100
    }

    # Frontend
    handle /* {
        reverse_proxy tracker-frontend-dev:3100
    }
}

JSON Configuration

Equivalent JSON Config

For those who prefer JSON configuration:

{
  "apps": {
    "http": {
      "servers": {
        "tracker": {
          "listen": [":80", ":443"],
          "routes": [
            {
              "match": [
                {
                  "host": ["tracker.local"]
                }
              ],
              "handle": [
                {
                  "handler": "subroute",
                  "routes": [
                    {
                      "match": [
                        {
                          "path": ["/api/*"]
                        }
                      ],
                      "handle": [
                        {
                          "handler": "reverse_proxy",
                          "upstreams": [
                            {
                              "dial": "api:8100"
                            }
                          ],
                          "headers": {
                            "request": {
                              "set": {
                                "Host": ["{http.request.host}"],
                                "X-Forwarded-Proto": ["{http.request.scheme}"]
                              }
                            }
                          }
                        }
                      ]
                    },
                    {
                      "match": [
                        {
                          "path": ["/static/*"]
                        }
                      ],
                      "handle": [
                        {
                          "handler": "reverse_proxy",
                          "upstreams": [
                            {
                              "dial": "api:8100"
                            }
                          ]
                        }
                      ]
                    },
                    {
                      "match": [
                        {
                          "path": ["/admin/*"]
                        }
                      ],
                      "handle": [
                        {
                          "handler": "reverse_proxy",
                          "upstreams": [
                            {
                              "dial": "tracker-admin-dev:3000"
                            }
                          ]
                        }
                      ]
                    },
                    {
                      "match": [
                        {
                          "path": ["/*"]
                        }
                      ],
                      "handle": [
                        {
                          "handler": "reverse_proxy",
                          "upstreams": [
                            {
                              "dial": "tracker-frontend-dev:3100"
                            }
                          ]
                        }
                      ]
                    }
                  ]
                }
              ]
            }
          ]
        }
      }
    }
  }
}

Docker Compose Integration

Adding Caddy to Docker Compose

services:
  caddy:
    image: caddy:2-alpine
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp" # HTTP/3
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config
    depends_on:
      - api
      - tracker-admin-dev
      - tracker-frontend-dev
    networks:
      - tracker-network
    environment:
      - CADDY_ADMIN=0.0.0.0:2019 # Admin API

  # Your existing services...
  api:
    # ... existing configuration

  tracker-admin-dev:
    # ... existing configuration

  tracker-frontend-dev:
    # ... existing configuration

volumes:
  caddy_data:
  caddy_config:

networks:
  tracker-network:
    driver: bridge

Environment-Based Configuration

# Caddyfile with environment variables
{$DOMAIN:tracker.local} {
    handle /api/* {
        reverse_proxy {$API_HOST:api}:{$API_PORT:8100} {
            header_up Host {http.request.host}
        }
    }

    handle /static/* {
        reverse_proxy {$API_HOST:api}:{$API_PORT:8100}
    }

    handle /admin/* {
        reverse_proxy {$ADMIN_HOST:tracker-admin-dev}:{$ADMIN_PORT:3000}
    }

    handle /* {
        reverse_proxy {$FRONTEND_HOST:tracker-frontend-dev}:{$FRONTEND_PORT:3100}
    }
}

HTTPS and TLS Configuration

Automatic HTTPS

Caddy automatically handles HTTPS for public domains:

# Automatic HTTPS for public domains
tracker.example.com {
    handle /api/* {
        reverse_proxy api:8100
    }

    handle /* {
        reverse_proxy tracker-frontend-dev:3100
    }
}

# Automatic redirect from HTTP to HTTPS
http://tracker.example.com {
    redir https://tracker.example.com{uri}
}

Custom TLS Configuration

tracker.local {
    # Custom TLS settings
    tls internal {
        protocols tls1.2 tls1.3
    }

    # Or use custom certificates
    # tls /path/to/cert.pem /path/to/key.pem

    handle /api/* {
        reverse_proxy api:8100
    }

    handle /* {
        reverse_proxy tracker-frontend-dev:3100
    }
}

Development Certificates

For local development with trusted certificates:

{
    # Global options
    local_certs  # Use local CA for development
}

tracker.local {
    handle /api/* {
        reverse_proxy api:8100
    }

    handle /* {
        reverse_proxy tracker-frontend-dev:3100
    }
}

Advanced Features

Rate Limiting

tracker.local {
    # Rate limiting
    handle /api/* {
        rate_limit {
            zone api_zone
            key {http.request.remote_host}
            events 100
            window 1m
        }
        reverse_proxy api:8100
    }

    handle /* {
        reverse_proxy tracker-frontend-dev:3100
    }
}

Request Logging

tracker.local {
    # Custom logging
    log {
        output file /var/log/caddy/tracker.log {
            roll_size 100mb
            roll_keep 5
            roll_keep_for 720h
        }
        format json
        level INFO
    }

    handle /api/* {
        reverse_proxy api:8100
    }

    handle /* {
        reverse_proxy tracker-frontend-dev:3100
    }
}

Metrics and Monitoring

{
    # Global options
    admin 0.0.0.0:2019  # Admin API for metrics
}

tracker.local {
    # Metrics endpoint
    handle /metrics {
        metrics /metrics
    }

    handle /api/* {
        reverse_proxy api:8100
    }

    handle /* {
        reverse_proxy tracker-frontend-dev:3100
    }
}

Troubleshooting

Common Issues

Domain Resolution:

# Add to /etc/hosts for local development
127.0.0.1 tracker.local
127.0.0.1 admin.tracker.local
127.0.0.1 api.tracker.local

Certificate Issues:

# Disable HTTPS for local development
tracker.local:80 {
    handle /api/* {
        reverse_proxy api:8100
    }

    handle /* {
        reverse_proxy tracker-frontend-dev:3100
    }
}

WebSocket Issues:

# WebSockets work automatically, but you can be explicit
tracker.local {
    handle /admin/* {
        reverse_proxy tracker-admin-dev:3000 {
            # WebSocket headers are handled automatically
        }
    }
}

Testing Configuration

# Validate Caddyfile
caddy validate --config Caddyfile

# Test configuration
caddy run --config Caddyfile

# Reload configuration
caddy reload --config Caddyfile

# Check if services are accessible
curl -I https://tracker.local/api/v1/health
curl -I https://tracker.local/admin/
curl -I https://tracker.local/

Debugging

{
    # Enable debug logging
    debug
}

tracker.local {
    # Detailed logging for troubleshooting
    log {
        level DEBUG
    }

    handle /api/* {
        reverse_proxy api:8100 {
            # Debug upstream
            header_up X-Debug-Upstream "api:8100"
        }
    }
}

Caddy vs Nginx Comparison

Advantages of Caddy

Automatic HTTPS: No manual certificate management ✅ Simpler Configuration: More readable and maintainable ✅ Modern Defaults: HTTP/2, HTTP/3 enabled by default ✅ Hot Reloading: Configuration changes without restart ✅ Built-in Features: Rate limiting, metrics, health checks ✅ JSON API: Programmatic configuration management

When to Use Caddy

  • Development Environments: Easier setup and configuration
  • Small to Medium Projects: Less complexity overhead
  • Automatic HTTPS Needs: Public domains with automatic certificates
  • Modern Protocol Support: HTTP/3, WebSocket, gRPC out of the box
  • API-First Approach: JSON configuration and admin API

Migration from Nginx

Most nginx configurations can be simplified in Caddy:

# Nginx
location /api/ {
    proxy_pass http://api:8100/api/;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
}
# Caddy equivalent
handle /api/* {
    reverse_proxy api:8100
}

Best Practices

Development

  • Use .local domains for local development
  • Enable debug logging for troubleshooting
  • Use environment variables for flexibility
  • Keep configurations simple and readable

Security

  • Let Caddy handle HTTPS automatically
  • Use rate limiting for API endpoints
  • Implement proper CORS policies
  • Monitor access logs

Performance

  • Enable compression (automatic in Caddy)
  • Use HTTP/2 and HTTP/3 (enabled by default)
  • Implement health checks for upstreams
  • Monitor metrics via admin API

This configuration provides a modern, simple alternative to nginx for development environments while maintaining compatibility with the production AWS ELB setup.