Files
dnswatcher/Dockerfile
T
clawbot e8b40062da
check / check (push) Successful in 4m6s
build: re-vendor canonical files from prompts dd4027b (closes #257)
The canonical files are fetched whole from sneak/prompts at dd4027b.
Kept after the canonical content: the livednstest deny entry in
.golangci.yml, /bin in .dockerignore, this repo's own .gitignore lines
and a [*.go] tab section in .editorconfig. The workflow keeps its
concurrency block and persist-credentials: false. Lint and test are
phases of the Dockerfile (golangci-lint v2.14.0; tests on the Debian
Go 1.25.7 image as an ordinary user, with the same flags); the build
stage depends on both and stamps the version the canonical way.
Dockerfile.lint is gone; the prettier stages of Dockerfile.fmt moved
into the Dockerfile. Every scripted docker build passes --no-cache;
script/cibuild bootstraps, runs script/check, then builds the image.
script/fmt-check absorbs fmt-check-go and fmt-check-markdown.

Model: opus-5-5
2026-10-06 04:29:48 +02:00

133 lines
5.6 KiB
Docker

# Lint phase, built alone by script/lint. The linter is invoked directly
# rather than through `make lint`, which is itself a docker build and
# would recurse into a daemon that does not exist in a build step.
# golangci/golangci-lint:v2.14.0 (Debian-based), 2026-10-06
FROM golangci/golangci-lint@sha256:ad862ba6b3798cbe0fd9fd7408d498fd74fbd2623a92406b2fd3898faf0bf98f AS lint
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN golangci-lint run --config .golangci.yml ./...
# Test phase, built alone by script/test. -race needs cgo and so a C
# compiler, which the Debian Go image ships and the alpine one does not.
# The tests query live DNS (TESTING.md), so this step needs the network.
# -count=1 keeps Go's test result cache out of both runs, as TESTING.md
# requires. -timeout 90s is a backstop above the 60-second cap on the
# suite. The rerun with -v only shows details: the build fails however
# it ends, because the first run already failed.
# golang 1.25.7-trixie, 2026-10-06
FROM golang@sha256:2b174ffcf56c7ad0c47d30d2630693265639ddf2a5141149c2da34db921791b4 AS test
# The file permission tests skip themselves as root, so the tests run as
# an ordinary user, whose home directory holds Go's build cache.
RUN useradd --create-home tester
USER tester
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go test -count=1 -race -timeout 90s -cover ./... || \
{ echo "--- Rerunning with -v for details ---"; \
go test -count=1 -race -timeout 90s -v ./...; exit 1; }
# Markdown formatting with prettier, at the version package.json and
# yarn.lock pin, so it is never installed on the host. script/fmt-check
# builds the fmt-check stage and script/fmt the fmt-out stage; the image
# does not depend on any of these stages, so a plain `docker build .`
# skips them.
# node:22-bookworm-slim, 2026-09-05
FROM node@sha256:83f487e0a63425e5b4d146fb5e5be574bcbe1b7b843d3ebafdd95eaf7767a7e5 AS nodedeps
# prettier lives outside /src, so the COPY of the repo cannot overwrite
# it and node_modules is not in the tree prettier walks.
WORKDIR /tools
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile --non-interactive --no-progress
ENV PATH="/tools/node_modules/.bin:${PATH}"
WORKDIR /src
# --config rather than discovery: a missing .prettierrc is then an error
# instead of prettier's defaults, under which every wrap passes.
# --no-editorconfig so .prettierrc alone sets the style.
FROM nodedeps AS fmt-check
COPY . .
RUN prettier --config .prettierrc --no-editorconfig --check "**/*.md"
# Only the markdown is copied out, with its paths, so the export cannot
# put anything else back over the working tree.
FROM nodedeps AS fmt
COPY . .
RUN prettier --config .prettierrc --no-editorconfig --write "**/*.md" && \
mkdir -p /out && \
find . -name '*.md' -type f -exec cp --parents '{}' /out/ ';'
FROM scratch AS fmt-out
COPY --from=fmt /out/ /
# Build stage. Nothing is wanted from the lint and test phases; the
# copies are what make BuildKit build them first, so this stage cannot
# run unless lint and test passed.
# golang 1.25-alpine, 2026-02-28
FROM golang@sha256:f6751d823c26342f9506c03797d2527668d095b0a15f1862cddb4d927a7a4ced AS builder
COPY --from=lint /src/go.sum /dev/null
COPY --from=test /src/go.sum /dev/null
RUN apk add --no-cache git
# A tar-stream context keeps the sender's file owners, which git refuses.
RUN git config --system --add safe.directory /src
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
# The VERSION build arg when one is given (script/docker and
# script/cibuild pass one), otherwise `git describe --tags --always` on
# the .git in the build context. With .git present, a version that is
# still empty, dev or unknown fails the build: git is missing or could
# not read the checkout.
ARG VERSION
RUN VERSION="${VERSION:-$(git describe --tags --always)}"; \
if [ -e .git ]; then \
case "$VERSION" in ""|dev|unknown) \
echo "version is '$VERSION' although .git is present" >&2; \
exit 1 ;; \
esac; \
fi; \
CGO_ENABLED=0 go build -trimpath \
-ldflags="-s -w -X main.Version=${VERSION}" \
-o /src/bin/dnswatcher ./cmd/dnswatcher/
# Runtime stage, and the last one: a plain `docker build .` builds this
# stage's chain and nothing else.
# alpine 3.21, 2026-02-28
FROM alpine@sha256:c3f8e73fdb79deaebaa2037150150191b9dcbfba68b4a46d70103204c53f4709
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
# 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
ENV DNSWATCHER_DATA_DIR=/var/lib/dnswatcher
# Config loading also reads a `.env` file and a file named `dnswatcher`
# (any config extension, or none) from the working directory. `/` holds
# neither, so every setting comes from the environment. Do not make the
# data directory, or the binary's directory, the working directory.
WORKDIR /
# No USER: the entrypoint must start as root to set up the data
# directory; it then runs dnswatcher as the dnswatcher user.
EXPOSE 8080
# busybox wget (already in alpine) probes the health endpoint every 10
# seconds, so the container is healthy well before upaas reads its health
# 60 seconds after a deploy and fails the deploy unless it is healthy.
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/docker-entrypoint.sh"]