TL;DR
I always leave ports 80 and 443 of the Docker host open for Caddy.
My intention is to leave only ports 25, 465, and 993 of the Docker host open for Stalwart.
I need to configure Caddy to forward all HTTPS communication directed to mail.mydomain.com to the Stalwart container.
Caddy should also not attempt to obtain a certificate, the Stalwart container itself will handle obtaining and renewing the certificate necessary for the email server to function.
Hi everyone!
I have a small VPS where I do my experiments and learning.
I’ve always been a fan of Caddy, I think it’s the best web server/reverse proxy. It’s simple to configure, a look at the Caddyfile and you can understand almost everything. I usually create a Docker network called “caddy_net” for the Caddy container and for the containers that will use Caddy as a reverse proxy for one or more of their services.
I reinstalled my VPS from scratch with Debian 13.4 and initially I will only have three Docker stacks: Caddy, Dockhand, and Stalwart.
The current Caddy configuration allows me to:
-
access Dockhand via HTTPS at
https://dockhand.mydomain.com -
view the default Caddy web server page at
https://vps.mydomain.com -
access Stalwart via HTTPS for initial configuration at
https://stalwart.mydomain.com
~/docker
├── caddy
│ ├── .env
│ ├── Caddyfile
│ ├── compose.yml
│ ├── config
│ ├── data
│ └── Dockerfile
└── stalwart
├── .env
├── compose.yml
├── data
└── etc
~/docker/caddy/Dockerfile
FROM caddy:2.11.3-builder AS builder
RUN xcaddy build \
--with github.com/caddy-dns/cloudflare \
--with github.com/mholt/caddy-events-exec \
--with github.com/mholt/caddy-l4
FROM caddy:2.11.3
COPY --from=builder /usr/bin/caddy /usr/bin/caddy
~/docker/caddy/.env
ACME_EMAIL=some.user@some.domain.com
CF_API_TOKEN=<your-api-token-goes-here>
PUID=1000
PGID=1000
TZ=America/Sao_Paulo
~/docker/caddy/compose.yml
services:
caddy:
image: caddy-custom:v2.11.3
build: .
pull_policy: build
user: "${PUID}:${PGID}"
hostname: caddy
container_name: caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:80"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
environment:
ACME_EMAIL: "${ACME_EMAIL}"
CF_API_TOKEN: "${CF_API_TOKEN}"
TZ: "${TZ:-America/Sao_Paulo}"
volumes:
- "./Caddyfile:/etc/caddy/Caddyfile"
- "./config:/config/caddy"
- "./data:/data/caddy"
cap_add:
- NET_BIND_SERVICE
networks:
default:
name: caddy_net
external: true
~/docker/caddy/Caddyfile
{
acme_ca https://acme-v02.api.letsencrypt.org/directory
acme_dns cloudflare {$CF_API_TOKEN}
email {$ACME_EMAIL}
renew_interval 45m
}
dockhand.mydomain.com {
reverse_proxy http://dockhand:3000
}
stalwart.mydomain.com {
reverse_proxy http://stalwart:8080
}
vps.mydomain.com {
root * /usr/share/caddy
file_server
}
~/docker/stalwart/.env
PUID=1000
PGID=1000
TZ=America/Sao_Paulo
~/docker/stalwart/compose.yml
services:
stalwart:
image: stalwartlabs/stalwart:v0.16.5
user: "${PUID}:${PGID}"
hostname: mail.mydomain.com
container_name: stalwart
restart: unless-stopped
ports:
- 25:25
- 465:465
- 993:993
environment:
TZ: "${TZ:-America/Sao_Paulo}"
volumes:
- "./etc:/etc/stalwart"
- "./data:/var/lib/stalwart"
networks:
default:
name: caddy_net
external: true
This is the complete list of Caddy modules added by the xcaddy build:
/srv $ caddy list-modules --skip-standard
caddy.listeners.layer4
dns.providers.cloudflare
events.handlers.exec
layer4
layer4.handlers.close
layer4.handlers.echo
layer4.handlers.proxy
layer4.handlers.proxy_protocol
layer4.handlers.socks5
layer4.handlers.subroute
layer4.handlers.tee
layer4.handlers.throttle
layer4.handlers.tls
layer4.handlers.vars
layer4.matchers.clock
layer4.matchers.dns
layer4.matchers.http
layer4.matchers.local_ip
layer4.matchers.not
layer4.matchers.openvpn
layer4.matchers.postgres
layer4.matchers.proxy_protocol
layer4.matchers.quic
layer4.matchers.rdp
layer4.matchers.regexp
layer4.matchers.remote_ip
layer4.matchers.remote_ip_list
layer4.matchers.socks4
layer4.matchers.socks5
layer4.matchers.ssh
layer4.matchers.tls
layer4.matchers.vars
layer4.matchers.vars_regexp
layer4.matchers.winbox
layer4.matchers.wireguard
layer4.matchers.xmpp
layer4.proxy.selection_policies.first
layer4.proxy.selection_policies.ip_hash
layer4.proxy.selection_policies.least_conn
layer4.proxy.selection_policies.random
layer4.proxy.selection_policies.random_choose
layer4.proxy.selection_policies.round_robin
tls.handshake_match.alpn
Non-standard modules: 43