opensourceprojects.dev

A broadsheet for software that doesn't ask for your email

Caddy reverse proxy for Docker containers, configured entirely through labels
GitHub RepoImpressions3

Project Description

View on GitHub

Stop Hand-Editing Your Caddyfile Every Time You Spin Up a Container

If you're running Docker in any serious capacity, you've probably hit this wall: you want a reverse proxy in front of your containers, but every new service means editing a config file, reloading the proxy, and hoping you didn't break something for the ten other services already running. Caddy-docker-proxy solves this by letting you configure Caddy entirely through Docker labels, so your proxy config lives right next to the service it belongs to.

What It Does

Caddy-docker-proxy is a plugin that turns Caddy into a reverse proxy for your Docker containers, driven by labels. It scans Docker metadata looking for labels that indicate a service or container should be served by Caddy. When it finds them, it generates an in-memory Caddyfile with site entries and proxies pointing to each Docker service by DNS name or container IP.

The interesting part is what happens next. Every time a Docker object changes, the plugin regenerates the Caddyfile and triggers Caddy to gracefully reload with zero downtime. So when you deploy a new container with the right labels, Caddy picks it up automatically. When you tear one down, the config updates to match. No manual intervention, no restart scripts, no stale entries pointing at containers that no longer exist.

It's written in Go and ships as a set of Docker images, which means you can drop it into an existing Docker Compose setup without much ceremony.

Why It's Cool

  • Your config lives with your service. Instead of maintaining a central Caddyfile that references every container you run, each service declares its own routing rules via labels. The whoami example from the README is about as minimal as it gets: two labels, and you've got a working HTTPS site.

  • Zero-downtime reloads. Caddy's graceful reload mechanism means config changes don't drop in-flight requests. That's a meaningful difference from tools that restart the proxy process on every change.

  • Automatic HTTPS comes along for free. Because this is Caddy under the hood, you get automatic certificate issuance from Let's Encrypt or ZeroSSL without configuring anything extra. Point a label at a domain and Caddy handles the rest.

  • Flexible execution modes. The plugin can run in server mode, controller mode, or standalone mode (the default). That means it can fit into different deployment topologies—whether you want it as a single all-in-one container or split across a controller and server setup.

  • Go template support in labels. The {{upstreams 80}} syntax shows up in the basic example, and the README documents template functions like upstreams that let you dynamically resolve backend addresses. This is what makes the labels-to-Caddyfile conversion more than just string substitution.

  • IPv6-aware networking. The README calls out a specific detail about creating your Docker network with --ipv6 so that Caddy and upstream services see actual client IPs rather than Docker's gateway address. It's the kind of thing that's easy to get wrong and annoying to debug later.

  • Labels map directly to Caddyfile concepts. Sites, snippets, global options, tokens, arguments—the conversion follows Caddyfile structure, so if you already know Caddy, you're not learning a new abstraction. You're just writing the same config in a different place.

How to Try It

The fastest path is Docker Compose. Start by creating a network for Caddy and your services:

docker network create caddy --ipv6

Then bring up Caddy itself:

services:
  caddy:
    image: lucaslorentz/caddy-docker-proxy:ci-alpine
    ports:
      - 80:80
      - 443:443/tcp
      - 443:443/udp
    environment:
      - CADDY_INGRESS_NETWORKS=caddy
    networks:
      - caddy
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - caddy_data:/data
    restart: unless-stopped

networks:
  caddy:
    external: true

volumes:
  caddy_data: {}

Run docker compose up -d, then deploy any service with labels like these:

services:
  whoami:
    image: traefik/whoami
    networks:
      - caddy
    labels:
      caddy: whoami.example.com
      caddy.reverse_proxy: "{{upstreams 80}}"

Visit https://whoami.example.com and you should hit the service, with a certificate issued automatically.

From there, the README covers the details you'll actually need: how labels convert to Caddyfile syntax, how to handle ordering and isolation, the difference between proxying services versus containers, and how to pick between the default and alpine images. There's also guidance on connecting to a remote Docker host, which matters if you're not running everything on one machine.

The repository is at github.com/lucaslorentz/caddy-docker-proxy.

Final Thoughts

This is a solid fit if you're already running Caddy or willing to adopt it, and you want your reverse proxy config to be self-describing per service. The label-driven approach isn't unique to this project—Traefik has done something similar for years—but if you prefer Caddy's automatic HTTPS and its config model, this gives you the same workflow without switching proxies. The main tradeoff is that your routing logic becomes distributed across your Compose files rather than centralized, which is either a feature or a headache depending on how you like to work. For small-to-medium deployments where services come and go, it's a clean way to keep things in sync without babysitting a config file.

Back to Projects
Project ID: c716b707-ba76-42df-9787-acfef25dab66Last updated: September 20, 2026 at 02:43 PM