Artistry & Intelligence

Enter the Atelier

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:

terminal
# 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:

terminal
# Create directory
mkdir -p /opt/caddy
cd /opt/caddy
docker-compose.yml
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. 1. Log into Cloudflare → My Profile → API Tokens
  2. 2. Create Token → Edit zone DNS template
  3. 3. Zone Resources → Include → Specific zone → your-domain.com
  4. 4. Copy API token
  5. 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:

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

See also in Process

Caddy Troubleshooting

Solutions for common caddy-docker-proxy issues

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:

Loading Johnny Castaway...

Original Johnny Castaway (1992) by Sierra On-Line/Dynamix
Recreation by xesf on GitHub
Click anywhere or press ESC to exit
Click anywhere or press ESC to exit
Recreation by Bryan Braun
Original Berkeley Systems After Dark (1989)
Click anywhere or press ESC to exit
supported.systems
MISSION CONTROL
CURRENT RELEASE v6.0.0-beta.18
LAUNCH DATE Mar 4, 2026
RECENT UPDATES
  • #15668 `1118ac4` Thanks @florian-lefebvre! - Changes Ty...
  • #15726 `6f19ecc` Thanks @ocavue! - Updates dependency `...
  • #15694 `66449c9` Thanks @matthewp! - Adds `preserveBuil...
All systems nominal
Click anywhere or press ESC to exit
The web framework for content-driven websites