From eb7a1f2d3b306005095300a45592480c2fc36e17 Mon Sep 17 00:00:00 2001 From: sneak Date: Thu, 1 Oct 2026 19:26:28 +0000 Subject: [PATCH] docker: report the real version in the image (closes #109) The Dockerfile builder stage takes ARG VERSION (default `dev`) and passes it to `make build` on the command line, which overrides the Makefile's `git describe` default. script/docker computes the version from `git describe` on the host and passes it as --build-arg VERSION, because .dockerignore leaves .git out of the build context and `git describe` inside the build only ever produced `dev`. A build that passes no argument, such as script/cibuild, still reports `dev`. `logger.Identify`, which logs `starting` with the version, was never called; `main` now calls it first, so the version is in the startup log. Model: opus-5-5 --- Dockerfile | 7 +++++-- Makefile | 2 ++ README.md | 14 +++++++++----- TODO.md | 3 ++- cmd/dnswatcher/main.go | 1 + script/docker | 10 +++++++++- 6 files changed, 28 insertions(+), 9 deletions(-) diff --git a/Dockerfile b/Dockerfile index 711588c..a76cbb6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -34,8 +34,11 @@ COPY . . # Run the tests - build fails if any test fails RUN make test -# Build the binary -RUN make build +# Build the binary. .dockerignore leaves out .git, so `git describe` in +# the Makefile cannot find the version here: script/docker passes it as +# --build-arg VERSION, and a build that passes none reports `dev`. +ARG VERSION=dev +RUN make build VERSION="${VERSION}" # Runtime stage # alpine 3.21, 2026-02-28 diff --git a/Makefile b/Makefile index 7b2447f..5d36bc7 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,8 @@ .PHONY: all bootstrap setup build lint fmt fmt-check test check clean hooks docker BINARY := dnswatcher +# `make build VERSION=...` overrides this; the Dockerfile does so, as the +# image has no .git to describe. VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev") LDFLAGS := -X main.Version=$(VERSION) diff --git a/README.md b/README.md index f971096..a074dbe 100644 --- a/README.md +++ b/README.md @@ -480,7 +480,8 @@ them. We provide: - `script/check` — run test, lint, and fmt-check - `script/docker` — build the Docker image tagged via `script/projectname`, with `--no-cache-filter=lint,builder` so the lint stage and the builder stage, - which runs the tests, run on every invocation + which runs the tests, run on every invocation, and with the version from + `git describe` passed as `--build-arg VERSION` - `script/cibuild` — CI entrypoint: `docker build` with `--no-cache-filter=lint,builder`, so the lint stage and the builder stage, which runs the tests, run on every invocation, because a cached build lints @@ -502,11 +503,14 @@ make clean # Remove build artifacts ### Build-Time Variables -Version is injected via `-ldflags`: +`make build` sets the version with `-ldflags "-X main.Version=..."`, taking +it from `git describe --tags --always --dirty`, or from `VERSION` when given +on the command line (`make build VERSION=1.2.3`). The version appears in the +startup log and in the health check response. -```sh -go build -ldflags "-X main.Version=$(git describe --tags --always)" ./cmd/dnswatcher -``` +The Docker image has no `.git`, so the `Dockerfile` takes the version as +`--build-arg VERSION`. `make docker` passes it; a plain `docker build` +passes none, and that image reports `dev`. --- diff --git a/TODO.md b/TODO.md index 8af2f80..e9b8d0e 100644 --- a/TODO.md +++ b/TODO.md @@ -20,6 +20,8 @@ https://git.eeqj.de/sneak/dnswatcher/issues/104 # Completed Steps +- 2026-10-01: the image built by `make docker` reports the `git describe` + version, not `dev`, and the startup log now shows it (closes #109). - 2026-10-01: `TODO.md` brought up to date: open issues listed by URL, every Completed Steps entry cut to at most two lines (closes #146). - 2026-10-01: wildcard CORS now applies only to the public routes, not to @@ -93,7 +95,6 @@ https://git.eeqj.de/sneak/dnswatcher/issues/104 https://git.eeqj.de/sneak/dnswatcher/issues/177 - rate limit on `/metrics` Basic Auth: https://git.eeqj.de/sneak/dnswatcher/issues/101 -- images report version `dev`: https://git.eeqj.de/sneak/dnswatcher/issues/109 - trial run of the finished image: https://git.eeqj.de/sneak/dnswatcher/issues/149 - 1.0 readiness: run it with a real config and read the logs: diff --git a/cmd/dnswatcher/main.go b/cmd/dnswatcher/main.go index baff13b..11e6780 100644 --- a/cmd/dnswatcher/main.go +++ b/cmd/dnswatcher/main.go @@ -63,6 +63,7 @@ func main() { return n }, ), + fx.Invoke(func(l *logger.Logger) { l.Identify() }), fx.Invoke(func(*server.Server, *watcher.Watcher) {}), ).Run() } diff --git a/script/docker b/script/docker index 4f1fd14..93d2408 100755 --- a/script/docker +++ b/script/docker @@ -12,7 +12,15 @@ ROOT="$(cd "$SCRIPT_DIR/.." && pwd -P)" main() { cd "$ROOT" - docker build --no-cache-filter=lint,builder -t "$("$SCRIPT_DIR/projectname")" . + # Own line: a failing command substitution inside an argument does + # not trip `set -e`, so the inline form degrades silently to an + # empty constant. VERSION is computed here because .dockerignore + # excludes .git, so `git describe` in a build stage cannot find it. + version="$(git describe --tags --always --dirty 2>/dev/null || true)" + [ -n "$version" ] || version="unknown" + docker build --no-cache-filter=lint,builder \ + --build-arg VERSION="$version" \ + -t "$("$SCRIPT_DIR/projectname")" . } main "$@"