Caddy needs this only for DNS-01 cert renewal via Vultr's API, which happens within the final 30 days of the cert's 90-day lifetime -- roughly once a quarter. Requiring it to be exported on every `docker compose up` was friction for routine ops (CoreDNS recreations during unrelated config changes). Empty default keeps the stack startable without the key in scope. When renewal is imminent, set the var properly OR (preferred long-term) migrate Caddy to caddy-dns/rfc2136 pointing at our own plugin and retire the Vultr dependency entirely.
81 lines
3.5 KiB
YAML
81 lines
3.5 KiB
YAML
services:
|
|
# Caddy runs as a dedicated ACME client + cert renewer. It provisions
|
|
# a Let's Encrypt cert for ${CADDY_HOSTNAME} via DNS-01 (Vultr API)
|
|
# and persists it to ./caddy-data. CoreDNS reads from that same path
|
|
# read-only. The container's HTTP/HTTPS ports are NOT published — we
|
|
# only care about the cert files on disk.
|
|
caddy:
|
|
build: ./caddy
|
|
container_name: coredns-caddy
|
|
restart: unless-stopped
|
|
environment:
|
|
- CADDY_HOSTNAME=${CADDY_HOSTNAME}
|
|
- ACME_EMAIL=${ACME_EMAIL}
|
|
# Optional: only required for Caddy's DNS-01 cert renewal via Vultr's
|
|
# API. Cert is valid ~90 days; this env var only matters within the
|
|
# final 30d renewal window. Empty default keeps `docker compose up`
|
|
# working without the key in scope. Set it when renewal is imminent,
|
|
# OR migrate Caddy to caddy-dns/rfc2136 (via our plugin) and retire
|
|
# the Vultr dependency entirely.
|
|
- VULTR_API_KEY=${VULTR_API_KEY:-}
|
|
volumes:
|
|
- ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro
|
|
- ./caddy-data:/data
|
|
- ./caddy-config:/config
|
|
healthcheck:
|
|
# Two-jobs-in-one: (1) maintain stable filenames (cert.pem / key.pem)
|
|
# as symlinks into Caddy's hostname-keyed storage, so the Corefile
|
|
# doesn't have to encode the hostname. (2) Flip to "healthy" once
|
|
# the symlink dereferences successfully (i.e. Caddy has issued).
|
|
# Relative symlink targets so paths work the same from host or
|
|
# from any container mounting this directory.
|
|
test:
|
|
- "CMD-SHELL"
|
|
- >
|
|
ln -sf certificates/acme-v02.api.letsencrypt.org-directory/${CADDY_HOSTNAME}/${CADDY_HOSTNAME}.crt /data/caddy/cert.pem &&
|
|
ln -sf certificates/acme-v02.api.letsencrypt.org-directory/${CADDY_HOSTNAME}/${CADDY_HOSTNAME}.key /data/caddy/key.pem &&
|
|
chmod 755 /data/caddy &&
|
|
chmod -R a+rX /data/caddy/certificates 2>/dev/null;
|
|
test -e /data/caddy/cert.pem
|
|
interval: 10s
|
|
timeout: 3s
|
|
retries: 60 # ~10 min ceiling for initial issuance
|
|
start_period: 5s
|
|
|
|
coredns:
|
|
image: ${COREDNS_IMAGE}
|
|
container_name: coredns
|
|
restart: unless-stopped
|
|
command: ["-conf", "/etc/coredns/Corefile"]
|
|
depends_on:
|
|
caddy:
|
|
condition: service_healthy
|
|
ports:
|
|
- "${DNS_PORT}:53/udp"
|
|
- "${DNS_PORT}:53/tcp"
|
|
- "${DOT_PORT}:853/tcp"
|
|
- "${DOH_PORT}:443/tcp"
|
|
- "${METRICS_PORT}:9153/tcp"
|
|
- "${HEALTH_PORT}:8080/tcp"
|
|
volumes:
|
|
- ./Corefile:/etc/coredns/Corefile:ro
|
|
- ./zones:/zones:ro
|
|
# Subpath mount of Caddy's data dir. The healthcheck maintains
|
|
# cert.pem / key.pem symlinks at the top of this tree, so CoreDNS
|
|
# sees stable filenames regardless of hostname. The /accounts dir
|
|
# (ACME registration private key) is sibling to /caddy and is NOT
|
|
# exposed to CoreDNS — only /caddy is mounted.
|
|
- ./caddy-data/caddy:/etc/coredns/certs:ro
|
|
# CoreDNS's official image is distroless (no shell, no wget/curl), so
|
|
# the conventional `wget /health` healthcheck silently fails forever
|
|
# and Docker reports the container as unhealthy. The coredns binary
|
|
# itself supports a version flag, which exits 0 only if the binary
|
|
# is runnable — a thin but honest liveness probe. For deeper checks,
|
|
# query :8081/health from outside the container (curl from the host).
|
|
healthcheck:
|
|
test: ["CMD", "/coredns", "-version"]
|
|
interval: 30s
|
|
timeout: 5s
|
|
retries: 3
|
|
start_period: 10s
|