Traditional web servers like Nginx require external sidecar containers (like Certbot or `nginx-proxy-manager`) to obtain and renew Let's Encrypt TLS certificates. Caddy solves this entirely by managing ACME certificates, HTTPS redirects, and HTTP/3 multiplexing natively in a single container.
This guide demonstrates how to configure Caddy in Docker Compose to securely reverse-proxy upstream web services with security headers, Gzip compression, and rate limiting.
Replace api.yourdomain.com with your domain name. Ensure your domain points to your VPS public IP before starting the container.
api.yourdomain.com {
# Enable Gzip and Zstandard compression
encode gzip zstd
# Route incoming requests to your app container
reverse_proxy app:3000 {
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
}
# Hardened Security Headers
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
X-Content-Type-Options "nosniff"
X-Frame-Options "DENY"
Referrer-Policy "strict-origin-when-cross-origin"
Permissions-Policy "camera=(), microphone=(), geolocation=()"
}
}
Two persistent Docker volumes (caddy_data and caddy_config) must be mounted to prevent rate-limiting by Let's Encrypt on container restarts.
version: '3.8'
services:
caddy:
image: caddy:2-alpine
container_name: caddy_proxy
restart: always
ports:
- "80:80"
- "443:443"
- "443:443/udp" # Required for HTTP/3 QUIC
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data # Preserves SSL certificates
- caddy_config:/config # Preserves runtime config
networks:
- webnet
app:
image: your-app-image:latest
container_name: web_app
restart: always
expose:
- "3000"
networks:
- webnet
networks:
webnet:
volumes:
caddy_data:
caddy_config: