At home, I run a number of docker containers on my Beelink S121. To easily access them with a memorable name, instead of http://192.168.1.2:823, I was running caddy-docker-proxy. It proxies docker containers with caddy through labels in a docker-compose.yml file. With it, I could access each container at https://my-service.lan.
The basic usage is quite simple. First of all, we need caddy-docker-proxy running (once per server, so you’ll serve multiple services running on their docker containers).
A sufficient docker-compose.yml looks like this:
services:
caddy:
image: caddy-docker-proxy
ports:
- 80:80
- 443:443
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: {}Then, to run f.e. adguard, I would use this docker-compose.yml:
services:
adguardhome:
container_name: adguardhome
image: adguard/adguardhome
restart: unless-stopped
volumes:
- $DATA/adguard/work:/opt/adguardhome/work
- $DATA/adguard/conf:/opt/adguardhome/conf
user: 1000:1000
networks:
- caddy
labels:
caddy: adguard.lan
caddy.reverse_proxy: "{{upstreams 80}}"
ports:
- "53:53/tcp"
- "53:53/udp"
networks:
caddy:
external: trueThis is all you need to access the service through https://adguard.lan.
However, you need to trust local certificates (my post about it is the most popular of my blog) if you don’t want to be bothered by safety warnings. Every. Single. Time.
Some time ago, however, I read that’s possible to issue real HTTPS certificates with Let’s Encrypt even if the docker container is not reachable from for the internet.
How?
The first step is to see whether caddy-dns supports your DNS registrar. Then, you need to create an API key and build a custom version of caddy-docker-proxy (in the example below, I’ve used porkbun but YMMV):
ARG CADDY_VERSION=2.11
FROM caddy:${CADDY_VERSION}-builder AS builder
RUN xcaddy build \
--with github.com/lucaslorentz/caddy-docker-proxy/v2 \
--with github.com/caddy-dns/porkbun
FROM caddy:${CADDY_VERSION}-alpine
COPY --from=builder /usr/bin/caddy /usr/bin/caddy
CMD ["caddy", "docker-proxy"](build this image with docker build -t caddy-caddy-docker-proxy-porkbun .)
Afterward, you can edit the previous compose files (note the changed image name and labels section):
services:
caddy:
image: caddy-docker-proxy-porkbun
ports:
- 80:80
- 443:443
environment:
- CADDY_INGRESS_NETWORKS=caddy
networks:
- caddy
labels:
caddy.acme_dns: porkbun
caddy.acme_dns.api_key: "$PORKBUN_API_KEY"
caddy.acme_dns.api_secret_key: "$PORKBUN_API_SECRET_KEY"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- caddy_data:/data
restart: unless-stopped
networks:
caddy:
external: true
volumes:
caddy_data: {}For adguard, update the labels.caddy key with the desired subdomain (and don’t forget to point your subdomain in your DNS registrar to the internal ip address where caddy-docker-proxy is running! In my case, that’s an A record pointing to 192.168.1.2)
version: '3.3'
services:
adguardhome:
container_name: adguardhome
image: adguard/adguardhome
restart: unless-stopped
volumes:
- $DATA/adguard/work:/opt/adguardhome/work
- $DATA/adguard/conf:/opt/adguardhome/conf
user: 1000:1000
networks:
- caddy
labels:
caddy: adguard.lanzani.nl
caddy.reverse_proxy: "{{upstreams 80}}"
ports:
- "53:53/tcp"
- "53:53/udp"
- "81:80/tcp"
- "3000:3000/tcp"
networks:
caddy:
external: trueAnd that’s it! Now https://adguard.lanzani.nl will be served without security warnings (while still being inaccessible from outside your network, if you set it up like me).
-
What a steal if I look at the May 2025 pricing (when I bought it) versus today. ↩︎