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