From 16c577d6d406f9c859f9e180d601b59e62efaacf Mon Sep 17 00:00:00 2001 From: clawbot <35+clawbot@noreply.example.org> Date: Tue, 29 Sep 2026 10:02:39 +0000 Subject: [PATCH] docker: set up the data directory in an entrypoint (closes #166) The runtime image no longer sets USER. Its new entrypoint, deploy/docker-entrypoint.sh, runs as root: it creates the data directory if needed, gives it and everything in it to the dnswatcher user (uid 10001) with mode 700 on the directory, then runs dnswatcher as that user with su-exec. A bind-mounted host directory, whether empty and root-owned or holding a state file left by another uid, no longer has to be chowned first, and the README's upaas section now says only which path to mount. The startup check that the data directory is writable stays. Model: opus-5-5 --- Dockerfile | 18 ++++++++---------- README.md | 12 +----------- TODO.md | 5 +++++ deploy/docker-entrypoint.sh | 17 +++++++++++++++++ 4 files changed, 31 insertions(+), 21 deletions(-) create mode 100755 deploy/docker-entrypoint.sh diff --git a/Dockerfile b/Dockerfile index b4f7273..711588c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -41,18 +41,15 @@ RUN make build # alpine 3.21, 2026-02-28 FROM alpine@sha256:c3f8e73fdb79deaebaa2037150150191b9dcbfba68b4a46d70103204c53f4709 -RUN apk add --no-cache ca-certificates tzdata +RUN apk add --no-cache ca-certificates tzdata su-exec COPY --from=builder /src/bin/dnswatcher /usr/local/bin/dnswatcher +COPY deploy/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh -# Run as an unprivileged user that owns the data directory. A fresh named -# volume inherits this ownership; a bind-mounted host directory must be -# owned by uid 10001 (see "Running under upaas" in README.md), or startup -# fails. +# dnswatcher runs as this unprivileged user. The entrypoint creates the +# data directory and gives it to this user on every start. RUN addgroup -S -g 10001 dnswatcher \ - && adduser -S -G dnswatcher -u 10001 dnswatcher \ - && mkdir -p /var/lib/dnswatcher \ - && chown dnswatcher:dnswatcher /var/lib/dnswatcher + && adduser -S -G dnswatcher -u 10001 dnswatcher ENV DNSWATCHER_DATA_DIR=/var/lib/dnswatcher @@ -62,7 +59,8 @@ ENV DNSWATCHER_DATA_DIR=/var/lib/dnswatcher # data directory, or the binary's directory, the working directory. WORKDIR / -USER dnswatcher +# No USER: the entrypoint must start as root to set up the data +# directory; it then runs dnswatcher as the dnswatcher user. EXPOSE 8080 @@ -72,4 +70,4 @@ EXPOSE 8080 HEALTHCHECK --interval=10s --timeout=5s --start-period=10s --retries=3 \ CMD wget -q -O /dev/null "http://127.0.0.1:${PORT:-8080}/.well-known/healthcheck" || exit 1 -ENTRYPOINT ["/usr/local/bin/dnswatcher"] +ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"] diff --git a/README.md b/README.md index 4704b23..e8c19da 100644 --- a/README.md +++ b/README.md @@ -533,17 +533,7 @@ repository's `Dockerfile` and runs it. The app needs: - **Branch:** `prod`. `prod` is cut from `main`, and merging a `main` to `prod` pull request is a deploy. - **Volume:** one host directory mounted at `/var/lib/dnswatcher`, where - the state file lives. upaas bind-mounts the host path it is given and - does not create it. The container runs as uid 10001 and does not start - unless it can write there. Create the directory before the first - deploy: - - ```sh - mkdir -p /path/to/data - chown 10001:10001 /path/to/data - chmod 700 /path/to/data - ``` - + the state file lives. - **Network and port:** the dashboard is unauthenticated and shows every watched name and recent alert, and upaas publishes every mapped port on all interfaces of the host diff --git a/TODO.md b/TODO.md index 1c50752..3c3c3c0 100644 --- a/TODO.md +++ b/TODO.md @@ -19,6 +19,11 @@ Rationale, Design, TODO, License, Author) if any are still missing. # Completed Steps +- 2026-09-29: the image sets up its own data directory. Its entrypoint, + `deploy/docker-entrypoint.sh`, starts as root, creates the data directory if + needed, gives it and everything in it to the `dnswatcher` user with mode 700 + on the directory, then runs dnswatcher as that user with `su-exec`. A + bind-mounted host directory no longer has to be chowned first (closes #166). - 2026-09-29: `.golangci.yml` re-fetched unchanged from `sneak/prompts`. It replaces the deprecated `gomodguard` with `gomodguard_v2`, so `make lint` no longer warns about it, and turns on `depguard` with the org `test-support` diff --git a/deploy/docker-entrypoint.sh b/deploy/docker-entrypoint.sh new file mode 100755 index 0000000..62b95cd --- /dev/null +++ b/deploy/docker-entrypoint.sh @@ -0,0 +1,17 @@ +#!/bin/sh +# deploy/docker-entrypoint.sh: the Docker image's ENTRYPOINT. It runs as +# root only to give the data directory to the dnswatcher user: a host +# directory bind-mounted there keeps its host owner, often root, and may +# hold a state file left by another uid, which dnswatcher could neither +# read nor replace. dnswatcher itself always runs as the dnswatcher user. +set -eu + +main() { + dir="${DNSWATCHER_DATA_DIR:-/var/lib/dnswatcher}" + mkdir -p "$dir" + chown -R dnswatcher:dnswatcher "$dir" + chmod 700 "$dir" + exec su-exec dnswatcher /usr/local/bin/dnswatcher "$@" +} + +main "$@" -- 2.54.0