The flagship Caddy ran its admin API on 0.0.0.0:2019, reachable by
every container on the Docker network. The admin API can rewrite
routes and proxy targets, so a journal/planner RCE could repoint
traffic with no further auth. (The brouter-host Caddyfile already
binds admin to localhost; the flagship didn't.)
The admin endpoint also served Prometheus metrics (global `metrics`
option → admin endpoint), and Prometheus scrapes caddy:2019
cross-container — so admin couldn't just move to loopback without
breaking metrics. Split them:
- admin localhost:2019 (loopback only). All reloads are in-container
(`docker compose exec caddy caddy reload`) so they use this endpoint
unaffected.
- a dedicated `:2020` server exposes the read-only metrics handler;
prometheus.yml now scrapes caddy:2020.
Validated with `caddy validate` + `caddy adapt` against caddy:2:
admin.listen=localhost:2019, srv on :2020 carries the metrics handler,
the :443 site is unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Single-file bind mounts (./foo.yml:/etc/foo.yml) pin to the host file's
inode at container-create time. The CD pipeline scp's a replacement file
(new inode), so the running container keeps reading the OLD inode —
`docker compose up -d` won't recreate on a content-only change, and
neither restart/SIGHUP/`caddy reload` re-reads the new file. Net effect:
config-only infra PRs deployed "successfully" but never took effect
(confirmed with PR #500's prometheus.yml; the WAL retention work only
applied because #498 also changed docker-compose.yml, forcing a
recreate). Caddyfile changes had the same latent gap.
Switch the four single-file config mounts to DIRECTORY mounts, which
resolve children live so a reload/restart picks up the new file:
- prometheus, loki, promtail: mount ./<svc> dir (loki/promtail
--config.file paths updated to the real filenames).
- caddy: move Caddyfile into caddy/ and mount the dir; ./sites overlays
/etc/caddy/sites (caddy/sites/.gitkeep keeps the mountpoint). Container
path /etc/caddy/Caddyfile is unchanged, so every `caddy reload --config
/etc/caddy/Caddyfile` ref still works. scp source paths in cd-infra and
cd-apps updated to ship the caddy/ dir.
cd-infra now applies config-only changes after `up -d`: SIGHUP prometheus
(zero downtime), restart loki+promtail (no SIGHUP reload), caddy reload
(graceful). Mirrored prometheus/loki mounts in docker-compose.dev.yml.
Validated: docker compose config (both files), caddy validate from the
new path, read-only-parent + sites-overlay mount mechanics, workflow YAML.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>