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.
| Onderdeel | Rol | Gepubliceerd op de host? |
|---|---|---|
| caddy-container | TLS-afhandeling, routering op hostnaam, doorverwijzing van HTTP naar HTTPS | Ja: 80/tcp, 443/tcp, 443/udp |
| web-netwerk | Gedeeld bridge-netwerk met ingebouwde DNS voor servicenamen | Nee |
| app-containers | Je diensten (webapps, API's, dashboards) | Nee, alleen bereikbaar via Caddy |
| caddy_data-volume | Certificaten, privésleutels, ACME-account | Nee, maar moet persistent zijn |
Stap voor stap: gewone Caddyfile met docker compose
- 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.
- 02.Maak het gedeelde netwerk één keer aan: `docker network create web`.
- 03.Maak `/opt/proxy/compose.yaml` en `/opt/proxy/Caddyfile` aan zoals hieronder, en voer dan `docker compose up -d` uit in `/opt/proxy`.
- 04.Koppel elke app-stack aan het externe `web`-netwerk en start die met `docker compose up -d`.
- 05.Volg de uitgifte van het eerste certificaat met `docker compose logs -f caddy` in `/opt/proxy`.
- 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: trueEen 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: trueGewone Caddyfile vs. caddy-docker-proxy
| Gewone Caddyfile | Labels van caddy-docker-proxy | |
|---|---|---|
| Configuratie staat in | Eén bestand dat je in git versiebeheert | Labels verspreid over compose-bestanden |
| Toegang tot Docker-socket | Niet nodig | Vereist (beveiligingsgevoelig) |
| Nieuwe dienst | Caddyfile bewerken, `caddy reload` uitvoeren | Labels toevoegen, de container starten |
| Image | Officiële `caddy:2` | Community-image gebouwd op Caddy |
| Debuggen | Het bestand direct lezen | De gegenereerde config in de logs bekijken |
| Het best voor | Kleine, stabiele VPS-opzetten | Veel 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.