From 18aa53bdc76a71b8a01fb9fd49e84da4a7d929d7 Mon Sep 17 00:00:00 2001 From: Ryan Malloy Date: Thu, 21 May 2026 13:01:36 -0600 Subject: [PATCH] prod-readiness: alpine runtime + uid:gid passthrough + git auto-commit working The final set of fixes to make the rfc2136 plugin truly operational in production: - coredns/Dockerfile: switch runtime stage from gcr.io/distroless to alpine:3.20. Distroless has no package manager and no shell, so `git commit` (called by the plugin's auto-commit code path) had no way to execute. Alpine adds ~10 MB image size but gives us git + a usable shell for debugging. - docker-compose.yml: `user: "${COREDNS_UID:-1003}:${COREDNS_GID:-1004}"`. The container runs as the host's rpm user (uid 1003/gid 1004 on dell01) so zone files the plugin writes are owned by rpm:rpm on the host -- not root. Without this the plugin would write root-owned files we couldn't read or git-edit. Defaults match dell01; override per-host via env if needed. - .env.example: documents COREDNS_IMAGE_TAG (CalVer; bump per build). Add COREDNS_UID/GID if you need to override on a host where rpm has different numeric ids. Combined with the bumped image tag (2026.05.21.2), the full end-to-end flow works: caddy/nsupdate -> TSIG verify -> plugin handler -> atomic file write -> git auto-commit -> auto plugin reload -> query returns new record. --- .env.example | 2 +- coredns/Dockerfile | 14 +++++++++++++- docker-compose.yml | 11 ++++++++++- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/.env.example b/.env.example index 180cbfc..bb2bddb 100644 --- a/.env.example +++ b/.env.example @@ -8,7 +8,7 @@ COMPOSE_PROJECT_NAME=coredns # Custom CoreDNS image tag (CalVer). Built locally via `docker compose # build coredns` using ./coredns/Dockerfile; pulls plugins from the # referenced git repos at build time. Bump this when re-rolling. -COREDNS_IMAGE_TAG=2026.05.21 +COREDNS_IMAGE_TAG=2026.05.21.1 # Legacy pin (no longer the active image; kept for emergency rollback # to upstream CoreDNS if the custom build needs to be reverted). COREDNS_IMAGE=coredns/coredns:1.11.3 diff --git a/coredns/Dockerfile b/coredns/Dockerfile index 88c0394..3f12133 100644 --- a/coredns/Dockerfile +++ b/coredns/Dockerfile @@ -46,7 +46,19 @@ RUN sed -i "/^cache:cache$/i rfc2136:${PLUGIN_REPO}" plugin.cfg && \ RUN make # ─── Stage 2: runtime ────────────────────────────────────────────── -FROM gcr.io/distroless/static-debian12 +# Switched from distroless to alpine specifically so the rfc2136 +# plugin's auto-commit can shell out to `git`. Distroless has no +# package manager and no shell, which would block git execution. +# Image grows ~10 MB; trade-off worth it for the audit trail. +FROM alpine:3.20 + +RUN apk add --no-cache git ca-certificates && \ + # Pre-create the user-id range the container will run as (1000) + # so that volume-mounted files written by this process land owned + # by the host's primary user. Add to the same group so a future + # interactive `docker exec --user 1000` works. + addgroup -g 1000 -S coredns && \ + adduser -u 1000 -S coredns -G coredns COPY --from=builder /build/coredns /coredns diff --git a/docker-compose.yml b/docker-compose.yml index 4fdff5b..1147848 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -53,6 +53,13 @@ services: image: coredns-rfc2136:${COREDNS_IMAGE_TAG} container_name: coredns restart: unless-stopped + # Run as host's primary user so files the rfc2136 plugin writes to + # /zones land owned by rpm:rpm on the host. Without this they'd + # be root-owned, making manual edits / git ops painful. + # + # UID/GID come from env (defaulted to dell01's rpm: 1003:1004). + # Override in .env for hosts where rpm has different ids. + user: "${COREDNS_UID:-1003}:${COREDNS_GID:-1004}" command: ["-conf", "/etc/coredns/Corefile"] # The Corefile uses {$ACME_TSIG_SECRET} expansion to read the # TSIG secret. Passed in from compose's env (which auto-reads .env). @@ -70,7 +77,9 @@ services: - "${HEALTH_PORT}:8080/tcp" volumes: - ./Corefile:/etc/coredns/Corefile:ro - - ./zones:/zones:ro + # Read-write because the rfc2136 plugin writes zone files in-place + # after each accepted UPDATE message (atomic temp-file + rename). + - ./zones:/zones # 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