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 1e6294b..f971096 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 23a1f8f..7462510 100644 --- a/TODO.md +++ b/TODO.md @@ -25,6 +25,8 @@ Rationale, Design, TODO, License, Author) if any are still missing. constructors: two moved to `export_test.go`, one is deleted (closes #111). - 2026-10-01: notify shutdown tests use one timing constant per meaning, name the bound they check, and require the drain's debug line (closes #116). +- 2026-09-29: the entrypoint chowns the data directory to `dnswatcher` and runs + dnswatcher as that user, so a host bind mount needs no chown (closes #166). - 2026-09-29: the live-DNS test package is renamed `internal/livednstest` and added to the `test-support` `deny` list in `.golangci.yml`, so `make lint` fails when program code imports it (closes #164). 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 "$@"