How-To Guide
Caddy Setup
Set up caddy-docker-proxy for automatic HTTPS, reverse proxying, and zero-configuration SSL.
What is caddy-docker-proxy?
caddy-docker-proxy is a Caddy plugin that automatically configures reverse proxying based on Docker container labels. It handles HTTPS certificates, routing, and load balancing without manual Caddyfile editing.
Benefits:
- • Automatic HTTPS certificates (Let's Encrypt)
- • Zero-configuration reverse proxy
- • Label-based routing (no Caddyfile)
- • Automatic certificate renewal
- • Support for multiple domains and services
Use case: You have multiple Docker services that need HTTPS. caddy-docker-proxy manages all routing and certificates automatically.
Prerequisites
- Linux server with Docker installed
- Domain name with DNS A record pointing to server
- Ports 80/443 open in firewall
- Root or sudo access to server
# Check ports are accessible
sudo netstat -tulpn | grep -E ':(80|443)'
# Should show nothing (ports available)
# Check Docker is running
docker ps Step 1: Create caddy network
All services using caddy-docker-proxy must be on the same Docker network:
# Create external network
docker network create caddy
# Verify creation
docker network ls | grep caddy Note: This is a one-time setup. All future services join this network.
Step 2: Deploy caddy-docker-proxy
Create a dedicated directory and docker-compose.yml:
# Create directory
mkdir -p /opt/caddy
cd /opt/caddy services:
caddy:
image: lucaslorentz/caddy-docker-proxy:ci-alpine
container_name: caddy-proxy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
# Docker socket (read-only)
- /var/run/docker.sock:/var/run/docker.sock:ro
# Certificate storage (persistent)
- caddy_data:/data
# Configuration storage
- caddy_config:/config
networks:
- caddy
environment:
# Enable automatic HTTPS
- CADDY_INGRESS_NETWORKS=caddy
# Cloudflare DNS (for wildcard certs)
- CLOUDFLARE_EMAIL=your-email@example.com
- CLOUDFLARE_API_TOKEN=your-cloudflare-token
labels:
# Disable proxy for caddy itself
caddy_ingress_networks: caddy
networks:
caddy:
external: true
volumes:
caddy_data:
caddy_config: Start caddy-docker-proxy:
docker compose up -d
# Check status
docker compose ps
# Expected:
# caddy-proxy Up 0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp
# Check logs
docker compose logs -f Step 3: Configure DNS provider (optional)
For wildcard certificates or DNS challenge, configure a DNS provider. Cloudflare example:
- 1. Log into Cloudflare → My Profile → API Tokens
- 2. Create Token → Edit zone DNS template
- 3. Zone Resources → Include → Specific zone → your-domain.com
- 4. Copy API token
- 5. Add to caddy docker-compose.yml environment
environment:
- CLOUDFLARE_EMAIL=your-email@example.com
- CLOUDFLARE_API_TOKEN=your-token-here Other providers: See libdns providers for other DNS services (AWS Route53, DigitalOcean, etc.).
Step 4: Configure service labels
Add Caddy labels to your application's docker-compose.yml:
services:
web:
image: your-app:latest
networks:
- caddy # Must be on caddy network
labels:
# Basic routing
caddy: your-domain.com
caddy.reverse_proxy: "{{upstreams 4321}}"
# TLS configuration
caddy.tls.dns: cloudflare
# Performance
caddy.encode: gzip
# Security headers
caddy.header: "X-Frame-Options DENY"
caddy.header.Strict-Transport-Security: "max-age=31536000"
caddy.header.X-Content-Type-Options: "nosniff"
networks:
caddy:
external: true | Label | Purpose |
|---|---|
caddy | Domain to route |
caddy.reverse_proxy | Upstream server (port) |
caddy.tls.dns | DNS provider for challenge |
caddy.encode | Compression (gzip/zstd) |
caddy.header | Response headers |
Advanced configurations
Multiple domains
labels:
caddy: domain1.com domain2.com www.domain1.com
caddy.reverse_proxy: "{{upstreams 4321}}" Path-based routing
labels:
caddy: domain.com
caddy.handle_path: "/api/*"
caddy.handle_path.reverse_proxy: "{{upstreams 8080}}"
caddy.handle_path.1: "/*"
caddy.handle_path.1_reverse_proxy: "{{upstreams 4321}}" Rate limiting
labels:
caddy: domain.com
caddy.reverse_proxy: "{{upstreams 4321}}"
caddy.rate_limit: "{remote_host} 100r/m" Custom logging
labels:
caddy: domain.com
caddy.reverse_proxy: "{{upstreams 4321}}"
caddy.log.output: "file /var/log/caddy/domain.log"
caddy.log.format: json Verify setup
Test that caddy-docker-proxy is working:
# Check certificate was issued
curl -I https://your-domain.com
# Expected:
# HTTP/2 200
# server: Caddy
# Verify certificate details
echo | openssl s_client -connect your-domain.com:443 -servername your-domain.com 2>/dev/null | openssl x509 -noout -subject -issuer -dates
# Check Caddy logs
docker logs caddy-proxy --tail=50
# Look for:
# - "certificate obtained successfully"
# - No error messages Common issues:
- • Certificate not issued: Check DNS records, wait for propagation (up to 48 hours)
- • 404 errors: Verify service is on caddy network, labels are correct
- • Timeout: Check upstream port matches service, container is running
- • Rate limit errors: Adjust or remove rate limit labels
Maintenance
Update caddy-docker-proxy
cd /opt/caddy
docker compose pull
docker compose up -d View certificates
# List stored certificates
docker exec caddy-proxy ls -lh /data/caddy/certificates
# Check certificate expiration
docker exec caddy-proxy cat /data/caddy/certificates/acme-v02.api.letsencrypt.org-directory/your-domain.com/your-domain.com.crt | openssl x509 -noout -dates Force certificate renewal
# Remove certificate (will auto-renew)
docker exec caddy-proxy rm -rf /data/caddy/certificates/acme-v02.api.letsencrypt.org-directory/your-domain.com
# Restart Caddy
docker restart caddy-proxy Next steps
caddy-docker-proxy configured. Now: