NoctisNode
← HandleidingenSelf-hosting2026-10-239 min

Caddy als Docker-reverse proxy: Compose-opzet met automatische HTTPS

Draai Caddy als Docker-reverse proxy: één compose-stack, een gedeeld netwerk, reverse_proxy naar servicenamen en automatische HTTPS, plus wanneer labels van caddy-docker-proxy helpen.

Kort gezegd: draai Caddy in een eigen container die poort 80 en 443 publiceert, koppel die en je app-containers aan één gedeeld Docker-netwerk en laat `reverse_proxy` naar elke servicenaam en interne poort wijzen (bijvoorbeeld `reverse_proxy app:3000`). Caddy resolvet de naam via de ingebouwde DNS van Docker en haalt automatisch een TLS-certificaat op voor elk siteadres.

Deze gids laat eerst de gewone Caddyfile-aanpak zien, daarna de variant met caddy-docker-proxy op basis van labels, en legt uit wanneer welke past op één VPS. Draai je Caddy direct op de host in plaats van in Docker, zie dan de basishandleiding voor Caddy als reverse proxy.

Hoe de onderdelen in elkaar passen

Alleen de Caddy-container publiceert poorten naar de buitenwereld. Elke backendcontainer sluit zich aan bij een door de gebruiker gedefinieerd bridge-netwerk (hier `web` genoemd) en stelt zijn poort alleen op dat netwerk beschikbaar. De ingebouwde DNS-server van Docker, beschikbaar op door de gebruiker gedefinieerde netwerken, laat Caddy `app:3000` bereiken via service- of containernaam, zodat je nooit container-IP's hardcodeert die bij elke herstart veranderen.

Backends ongepubliceerd laten is belangrijker dan het lijkt: Docker schrijft zijn eigen iptables-regels, dus een poort die met `ports:` is gepubliceerd kan vanaf internet bereikbaar zijn, ook als ufw zegt dat hij geblokkeerd is. Gebruik `expose:` of helemaal geen poortmapping voor alles wat achter de proxy hoort.

OnderdeelRolGepubliceerd op de host?
caddy-containerTLS-afhandeling, routering op hostnaam, doorverwijzing van HTTP naar HTTPSJa: 80/tcp, 443/tcp, 443/udp
web-netwerkGedeeld bridge-netwerk met ingebouwde DNS voor servicenamenNee
app-containersJe diensten (webapps, API's, dashboards)Nee, alleen bereikbaar via Caddy
caddy_data-volumeCertificaten, privésleutels, ACME-accountNee, maar moet persistent zijn

Stap voor stap: gewone Caddyfile met docker compose

  1. 01.Laat vóór het starten van Caddy voor elke hostnaam een A-record (en een AAAA-record als je IPv6 gebruikt) naar de VPS wijzen, anders mislukt de uitgifte van certificaten.
  2. 02.Maak het gedeelde netwerk één keer aan: `docker network create web`.
  3. 03.Maak `/opt/proxy/compose.yaml` en `/opt/proxy/Caddyfile` aan zoals hieronder, en voer dan `docker compose up -d` uit in `/opt/proxy`.
  4. 04.Koppel elke app-stack aan het externe `web`-netwerk en start die met `docker compose up -d`.
  5. 05.Volg de uitgifte van het eerste certificaat met `docker compose logs -f caddy` in `/opt/proxy`.
  6. 06.Herlaad na het bewerken van de Caddyfile zonder downtime: `docker compose exec -w /etc/caddy caddy caddy reload`.
# /opt/proxy/compose.yaml
services:
  caddy:
    image: caddy:2
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"   # HTTP/3
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data      # certificates and ACME account keys
      - caddy_config:/config
    networks:
      - web

volumes:
  caddy_data:
  caddy_config:

networks:
  web:
    external: true

Een app koppelen en de Caddyfile schrijven

Elke applicatiestack declareert hetzelfde externe netwerk. De servicenaam wordt de hostnaam waarmee Caddy verbinding maakt. Het volume `caddy_data` mag je nooit kwijtraken: als je het verwijdert, moet Caddy elk certificaat opnieuw aanvragen, wat tegen de rate limits van ACME kan aanlopen.

# /opt/app/compose.yaml
services:
  app:
    image: ghcr.io/example/app:latest
    restart: unless-stopped
    expose:
      - "3000"        # reachable on the network, NOT published on the host
    networks:
      - web

networks:
  web:
    external: true

# /opt/proxy/Caddyfile
{
	email [email protected]
}

app.example.com {
	reverse_proxy app:3000
}

grafana.example.com {
	reverse_proxy grafana:3000
}

api.example.com {
	reverse_proxy api-1:8080 api-2:8080 {
		lb_policy round_robin
		health_uri /healthz
		health_interval 10s
	}
}

Caddy-backends: upstreams, load balancing en health checks

In Caddy-termen is een backend een upstream van `reverse_proxy`. Je kunt meerdere upstreams in één directive opnemen, een `lb_policy` kiezen zoals `round_robin`, `least_conn` of `ip_hash`, en actieve health checks toevoegen met `health_uri`, zodat gefaalde containers uit de rotatie vallen. Passieve checks (`fail_duration`, `max_fails`) zijn ook nuttig als de app geen health-endpoint heeft.

Caddy zet standaard `X-Forwarded-For`, `X-Forwarded-Proto` en `X-Forwarded-Host` op doorgestuurde verzoeken. Configureer je app zo dat hij het netwerkbereik van de proxy vertrouwt, zodat hij het echte client-IP ziet en `https://`-links bouwt. WebSockets en gRPC werken via `reverse_proxy` zonder extra instellingen; gebruik voor gRPC zonder versleuteling `reverse_proxy h2c://service:50051`.

Labels in plaats van een Caddyfile: caddy-docker-proxy

Wie zoekt naar een "Caddy proxy manager" wil meestal wat Nginx Proxy Manager biedt: een container toevoegen en een hostnaam en certificaat krijgen zonder een centraal bestand te bewerken. Caddy heeft geen officiële GUI, maar het communityproject caddy-docker-proxy (github.com/lucaslorentz/caddy-docker-proxy) genereert de Caddyfile uit containerlabels en herlaadt Caddy wanneer containers starten of stoppen.

Het heeft leestoegang tot de Docker-socket nodig, wat in feite root op de host is. Mount die read-only, houd de proxy-image up-to-date en kies liever de gewone Caddyfile als je maar een handvol diensten draait.

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

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

volumes:
  caddy_data:

networks:
  web:
    external: true

Gewone Caddyfile vs. caddy-docker-proxy

Gewone CaddyfileLabels van caddy-docker-proxy
Configuratie staat inEén bestand dat je in git versiebeheertLabels verspreid over compose-bestanden
Toegang tot Docker-socketNiet nodigVereist (beveiligingsgevoelig)
Nieuwe dienstCaddyfile bewerken, `caddy reload` uitvoerenLabels toevoegen, de container starten
ImageOfficiële `caddy:2`Community-image gebouwd op Caddy
DebuggenHet bestand direct lezenDe gegenereerde config in de logs bekijken
Het best voorKleine, stabiele VPS-opzettenVeel kortlevende of vaak gewijzigde diensten

Draaien op een VPS

Op een NoctisNode-KVM-VPS krijg je volledige root en een dedicated IPv4, dus 80 en 443 publiceren vanuit één Caddy-container en al het andere verbergen is eenvoudig. Een Wisp (1 vCPU, 1 GB RAM) volstaat voor Caddy plus een paar lichte containers; zwaardere stacks passen op Pulse of Umbra. Details over certificaatuitgifte en foutsituaties staan in hoe automatische HTTPS in Caddy werkt, en het doorsturen van ruwe TCP of SSH in TCP- en SSH-proxying met Caddy. Scherm SSH op de host af met de handleiding voor SSH-hardening.

Veelgestelde vragen

QHoe laat ik Caddy als reverse proxy naar een Docker-container doorsturen?

AZet Caddy en de container op hetzelfde door de gebruiker gedefinieerde Docker-netwerk en gebruik `reverse_proxy service-name:port` in de Caddyfile. Gebruik geen `localhost`, want binnen de Caddy-container verwijst dat naar Caddy zelf.

QWaarom geeft Caddy 502 Bad Gateway voor mijn container?

AMeestal zitten de containers niet op een gedeeld netwerk, is de poort verkeerd (gebruik de interne poort van de container, niet een gepubliceerde hostpoort), of luistert de app binnen zijn container alleen op 127.0.0.1. Laat de app aan 0.0.0.0 binden en controleer `docker network inspect web`.

QIs caddy-docker-proxy veilig om te gebruiken?

AHet werkt goed, maar het vereist de Docker-socket, die controle op rootniveau over de host geeft. Mount die read-only, houd de image up-to-date en stel het admin-endpoint van Caddy niet bloot.

QWaar slaat Caddy certificaten op in Docker?

AIn `/data` binnen de officiële image, dus mount daar een named volume. Raak je het kwijt, dan moet elk certificaat opnieuw worden uitgegeven en loop je mogelijk tegen de rate limits van ACME aan.

QBestaat er een Caddy proxy manager met een web-UI?

ACaddy zelf heeft geen officiële web-UI; het wordt geconfigureerd met een Caddyfile, JSON of de admin-API. Tools op basis van labels zoals caddy-docker-proxy bieden een vergelijkbare werkwijze: container toevoegen en klaar.

Caddy als Docker-reverse proxy: Compose-opzet met automatische HTTPS · NoctisNode