coredns/docker-compose.yml
Ryan Malloy 3720cd2885 deploy: enable rfc2136 plugin for all 84 production zones
Wires the custom CoreDNS image (built via coredns/Dockerfile, source
includes git.supported.systems/rsp2k/coredns-rfc2136) into production:

- docker-compose.yml: switch coredns service from upstream image pin
  to a build target. New `image: coredns-rfc2136:${COREDNS_IMAGE_TAG}`
  is locally-built; `up -d coredns` triggers the build.
- .env: COREDNS_IMAGE_TAG=2026.05.21 (CalVer). Old COREDNS_IMAGE kept
  as a comment for emergency rollback to upstream 1.11.3.
- Corefile: new rfc2136 directive inside (common) snippet enumerating
  all 84 zones currently in zones/. Plugin is now in the chain for
  every server block (plain DNS, DoT, DoH). UPDATE opcode lands in
  the plugin handler; auto-commit on, CalVer SOA serial bumping on,
  zones-dir /zones matches the existing bind-mount.

TSIG key is read from ${ACME_TSIG_SECRET} which lives in .env.local
(gitignored). Production deployment needs that file synced to dell01
separately.

This commit DOESN'T trigger the deployment by itself -- the image
must be built on dell01 and the container recreated to apply.
2026-05-21 12:17:20 -06:00

88 lines
3.8 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:
# Custom build with the rfc2136 plugin baked in. The image tag is
# CalVer (set in .env COREDNS_IMAGE_TAG) so we can pin specific
# builds; `docker compose build coredns` produces the locally-tagged
# image, then up -d picks it up.
build:
context: .
dockerfile: coredns/Dockerfile
image: coredns-rfc2136:${COREDNS_IMAGE_TAG}
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