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..36810af 100644 --- a/TODO.md +++ b/TODO.md @@ -19,6 +19,12 @@ 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 created or chowned first, + and the README no longer asks for it (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 "$@"