docker: report the real version in the image (closes #109)
check / check (push) Successful in 1m10s

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`.

Model: opus-5-5
This commit is contained in:
2026-10-01 19:26:28 +00:00
parent 3390d7065e
commit 762ec83737
5 changed files with 27 additions and 8 deletions
+5 -2
View File
@@ -34,8 +34,11 @@ COPY . .
# Run the tests - build fails if any test fails # Run the tests - build fails if any test fails
RUN make test RUN make test
# Build the binary # Build the binary. .dockerignore leaves out .git, so `git describe` in
RUN make build # 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 # Runtime stage
# alpine 3.21, 2026-02-28 # alpine 3.21, 2026-02-28
+2
View File
@@ -1,6 +1,8 @@
.PHONY: all bootstrap setup build lint fmt fmt-check test check clean hooks docker .PHONY: all bootstrap setup build lint fmt fmt-check test check clean hooks docker
BINARY := dnswatcher 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") VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev")
LDFLAGS := -X main.Version=$(VERSION) LDFLAGS := -X main.Version=$(VERSION)
+9 -5
View File
@@ -480,7 +480,8 @@ them. We provide:
- `script/check` — run test, lint, and fmt-check - `script/check` — run test, lint, and fmt-check
- `script/docker` — build the Docker image tagged via `script/projectname`, with - `script/docker` — build the Docker image tagged via `script/projectname`, with
`--no-cache-filter=lint,builder` so the lint stage and the builder stage, `--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 - `script/cibuild` — CI entrypoint: `docker build` with
`--no-cache-filter=lint,builder`, so the lint stage and the builder stage, `--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 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 ### 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 The Docker image has no `.git`, so the `Dockerfile` takes the version as
go build -ldflags "-X main.Version=$(git describe --tags --always)" ./cmd/dnswatcher `--build-arg VERSION`. `make docker` passes it; a plain `docker build`
``` passes none, and that image reports `dev`.
--- ---
+2
View File
@@ -19,6 +19,8 @@ Rationale, Design, TODO, License, Author) if any are still missing.
# Completed Steps # Completed Steps
- 2026-10-01: the image built by `make docker` reports the `git describe`
version instead of `dev`, passed as `--build-arg VERSION` (closes #109).
- 2026-10-01: wildcard CORS now applies only to the public routes, not to - 2026-10-01: wildcard CORS now applies only to the public routes, not to
`/metrics`, and allows only the methods they serve (closes #100). `/metrics`, and allows only the methods they serve (closes #100).
- 2026-10-01: `internal/state` and `internal/watcher` no longer export test-only - 2026-10-01: `internal/state` and `internal/watcher` no longer export test-only
+9 -1
View File
@@ -12,7 +12,15 @@ ROOT="$(cd "$SCRIPT_DIR/.." && pwd -P)"
main() { main() {
cd "$ROOT" 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 "$@" main "$@"