Configuring Caddy as a Reverse Proxy for Docker: Say Goodbye to SSL Hassles and Complex Configs

Docker tutorial - IT technology blog
Docker tutorial - IT technology blog

The Nightmare Named Nginx Config and Certbot

Nginx is still a monument, but let’s be honest, managing config files hundreds of lines long is a nightmare. I once spent 3 hours just debugging why Certbot couldn’t renew SSL for a Docker sub-domain. The problem usually lies in overlapping redirect rules or errors in the .well-known configuration.

If you are managing about 5-10 microservices, repeating blocks of proxy_pass, header, and ssl_certificate configurations is a real waste of time. Caddy comes in as a lifesaver. Instead of forcing you to do everything manually, Caddy defaults everything to be automatic, especially Automatic HTTPS. It automatically issues and renews SSL from Let’s Encrypt without requiring any additional plugins.

Why Does Caddy ‘Outperform’ Nginx in a Docker Environment?

  • Ultra-minimalist configuration: A complete HTTPS website takes exactly 3 lines of code.
  • HTTP/3 by default: Caddy supports HTTP/3 (QUIC) out of the box, speeding up page load times by about 20-30% compared to the aging HTTP/2.
  • 100% SSL Automation: Forget Certbot. Caddy handles everything from A to Z, including certificate rotation when they are near expiration.
  • Written in Go: No system library dependencies, runs extremely light as a single binary inside the container.

Step 1: Setting Up a Common Docker Network

For Caddy to act as the “gatekeeper,” it needs a private path to communicate with other containers. We will create a network named caddy_net. This helps secure your system: internal apps don’t need to expose ports externally; only Caddy is allowed to receive traffic from ports 80 and 443.

docker network create caddy_net

In practice, this approach has helped me reduce port scanning risks by up to 90% because services like Database or Redis are hidden deep inside this network.

Step 2: Deploying Caddy with Docker Compose

Create a caddy directory to store all configurations. The standard directory structure will look like this:

caddy/
├── docker-compose.yml
└── Caddyfile

Content of the docker-compose.yml file:

version: "3.9"
services:
  caddy:
    image: caddy:2.7-alpine
    container_name: caddy_proxy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp" # Important for running HTTP/3
    networks:
      - caddy_net
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
      - caddy_config:/config

networks:
  caddy_net:
    external: true

volumes:
  caddy_data:
  caddy_config:

Warning: You must never forget to mount the volume for /data. This is where SSL certificates are stored. If you lose this data and restart containers repeatedly, you will be IP-blocked by Let’s Encrypt (Rate Limit) for 7 days due to excessive certificate requests.

Step 3: Configuring the Caddyfile – The Power of Simplicity

Suppose you have a Node.js app running in a container named my_awesome_app on port 3000. With Nginx, it takes 20 lines. With Caddy, all you need is:

app.yourdomain.com {
    reverse_proxy my_awesome_app:3000
}

Can you believe it? That’s it. Caddy will automatically understand you need SSL, fetch the cert, and configure the proxy. If you want to add security and compress data for Google PageSpeed optimization, use this structure:

api.yourdomain.com {
    encode gzip zstd
    header {
        Strict-Transport-Security "max-age=31536000;"
        X-Content-Type-Options nosniff
    }
    reverse_proxy api_container:8080
}

Step 4: Activation and Monitoring

Run the following command to start the system:

docker-compose up -d

To see if Caddy is working, check its logs. This is how I check if the SSL certificate has been issued:

docker logs -f caddy_proxy

When you see the log entry certificate obtained successfully, you can open your browser and enjoy the green padlock icon without breaking a sweat.

Real-world Experience: When Does Caddy Fail?

In actual operation, the most common error is 502 Bad Gateway. If you encounter this, check if your application container has joined caddy_net. Caddy cannot connect to what it cannot see.

A note for those using Cloudflare: Set the SSL/TLS mode to “Full (strict)”. If left at “Flexible,” you will fall into a redirect loop (Too many redirects) because Caddy tries to force HTTPS while Cloudflare sends HTTP requests to the server.

Caddy is not just a tool; it’s a way for us to work smarter. Instead of wasting time on cumbersome configs, let Caddy handle the infrastructure, while you focus on writing code and optimizing your product.

Share: