coredns: production Let's Encrypt cert via Caddy sidecar (DNS-01 + Vultr)

Replaces the self-signed dev cert flow with a real LE prod cert for
dns.l.supported.systems, issued and auto-renewed by a Caddy sidecar
using DNS-01 challenge against the Vultr API.

Components:
- caddy/Dockerfile builds Caddy 2.10.0 with caddy-dns/vultr plugin
  via xcaddy. GOTOOLCHAIN=auto so xcaddy can fetch newer Go on demand
  when plugin versions advance their minimum Go.
- caddy/Caddyfile uses DNS-01 with explicit public resolvers (1.1.1.1,
  9.9.9.9) for the propagation check. Without that, Docker's embedded
  DNS leaks the container into the host's split-horizon LAN DNS, which
  returns LAN IPs for ns1.vultr.com and the propagation check fails.
- docker-compose: caddy service shares ./caddy-data with coredns via a
  read-only subpath mount that excludes /acme (account private key).
- Healthcheck doubles as a symlinker: maintains stable cert.pem /
  key.pem names at /data/caddy/ and chmods cert files + their dirs to
  be readable by CoreDNS's nonroot user. Flips to "healthy" only once
  the symlinks dereference (i.e. cert exists), gating CoreDNS start
  via depends_on: service_healthy.
- Corefile unchanged — same /etc/coredns/certs/cert.pem path; only the
  bind-mount source switches from ./certs to ./caddy-data/caddy.
- New Makefile target: tls-up orchestrates the bring-up sequence.

Cert is valid until Aug 12 2026. Verified end-to-end:
  dig @127.0.0.1 -p 8853 +tls +tls-hostname=dns.l.supported.systems ...
  dig @127.0.0.1 -p 8443 +https +tls-hostname=dns.l.supported.systems ...
This commit is contained in:
Ryan Malloy 2026-05-14 01:34:57 -06:00
parent 066ba1892a
commit c1afe77b27
6 changed files with 195 additions and 32 deletions

12
.env
View file

@ -16,3 +16,15 @@ DOT_PORT=8853
# DoH (DNS-over-HTTPS, RFC 8484) — typically 443. Host port 8443
# because Caddy already owns 443 on this host.
DOH_PORT=8443
# --- Production cert provisioning (Caddy sidecar + Let's Encrypt) ---
# Hostname the cert is issued for. Must be a name you control and that
# resolves via the public DNS server holding the zone (Vultr's NS).
CADDY_HOSTNAME=dns.l.supported.systems
# Contact email registered with Let's Encrypt for expiry notifications.
ACME_EMAIL=rpm@malloys.us
# VULTR_API_KEY is intentionally NOT stored here. Caddy reads it from
# the shell environment via docker compose's variable interpolation —
# export it in your shell (or in ~/.zshenv) before `make tls-up`.