docker: a plain docker build . stamps the git version (closes #210)
check / check (push) Successful in 1m33s

A plain `docker build .`, which is how upaas builds, stamped `dev`:
`.dockerignore` left out `.git` and the builder declared
`ARG VERSION=dev`. `.dockerignore` now sends `.git` and lists no tracked
file, which git in the build would count as deleted and mark `-dirty`.
`ARG VERSION` has no default. The Makefile takes `VERSION` from the
environment with `?=`, so a build arg still wins (`script/docker` keeps
passing one); otherwise `git describe` runs in the builder. A new
`make version` prints the version, and the builder fails when the
context carries `.git` and it comes out empty, `dev` or `unknown`.

Model: opus-5-5
This commit is contained in:
2026-10-02 00:59:44 +00:00
parent c9510a986c
commit 3c1dfe2575
7 changed files with 51 additions and 27 deletions
+4 -7
View File
@@ -1,9 +1,6 @@
.git/ # .git is sent: the builder stage derives the version it stamps into the
# binary from it. No tracked file may be listed here: git in the build
# would see it as deleted and mark the version -dirty, and an excluded .md
# would silently drop out of the prettier check in Dockerfile.fmt.
bin/ bin/
node_modules/ node_modules/
# No .md may be excluded: Dockerfile.fmt checks every document with
# prettier, and an exclusion here would drop a file from that check while
# prettier still reports every file it was handed clean.
LICENSE
.editorconfig
.gitignore
+19 -5
View File
@@ -36,11 +36,25 @@ 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. .dockerignore leaves out .git, so `git describe` in # Version stamped into the binary: the VERSION build arg when one is
# the Makefile cannot find the version here: script/docker passes it as # given (script/docker passes one), otherwise what `git describe` says of
# --build-arg VERSION, and a build that passes none reports `dev`. # the .git in the build context, so a plain `docker build .` of a clone
ARG VERSION=dev # stamps its tag or short commit. The build arg reaches make through the
RUN make build VERSION="${VERSION}" # environment.
ARG VERSION
# A context that carries .git must yield a real version: one that is
# empty, `dev` or `unknown` cannot be traced back to a commit.
RUN version="$(make version)"; \
if [ -d .git ]; then \
case "$version" in \
"" | dev | unknown) \
echo "version is \"$version\" although the build context carries .git" >&2; \
exit 1 ;; \
esac; \
fi
RUN make build
# Runtime stage # Runtime stage
# alpine 3.21, 2026-02-28 # alpine 3.21, 2026-02-28
+2 -3
View File
@@ -30,9 +30,8 @@ COPY . .
# --config, not discovery: a .prettierrc that failed to arrive would # --config, not discovery: a .prettierrc that failed to arrive would
# otherwise leave prettier on its defaults, where proseWrap is "preserve" # otherwise leave prettier on its defaults, where proseWrap is "preserve"
# and every wrap this check exists to enforce passes. Missing the file is # and every wrap this check exists to enforce passes. Missing the file is
# a hard error instead. --no-editorconfig for the same reason in reverse: # a hard error instead. --no-editorconfig so that .prettierrc alone sets
# .editorconfig is not in the build context, so honouring it here and on # the style.
# a developer's machine would be two different answers.
RUN prettier --config .prettierrc --no-editorconfig --check "**/*.md" RUN prettier --config .prettierrc --no-editorconfig --check "**/*.md"
# Write path. Not a check: script/fmt builds this and takes the files. # Write path. Not a check: script/fmt builds this and takes the files.
+9 -4
View File
@@ -1,9 +1,10 @@
.PHONY: all bootstrap setup build lint fmt fmt-check test check clean hooks docker .PHONY: all bootstrap setup build version lint fmt fmt-check test check clean hooks docker
BINARY := dnswatcher BINARY := dnswatcher
# `make build VERSION=...` overrides this; the Dockerfile does so, as the # VERSION given on the command line (`make build VERSION=...`) or in the
# image has no .git to describe. # environment, which is how the Dockerfile's VERSION build arg arrives,
VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev") # wins over what `git describe` says of this checkout.
VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev")
LDFLAGS := -X main.Version=$(VERSION) LDFLAGS := -X main.Version=$(VERSION)
# Standard targets are thin shims; the implementations live in script/ # Standard targets are thin shims; the implementations live in script/
@@ -21,6 +22,10 @@ setup:
build: build:
go build -ldflags "$(LDFLAGS)" -o bin/$(BINARY) ./cmd/dnswatcher go build -ldflags "$(LDFLAGS)" -o bin/$(BINARY) ./cmd/dnswatcher
# Prints the version `make build` stamps; the Dockerfile checks it.
version:
@echo "$(VERSION)"
test: test:
@script/test @script/test
+13 -6
View File
@@ -534,6 +534,7 @@ provide:
```sh ```sh
make build # Build binary to bin/dnswatcher make build # Build binary to bin/dnswatcher
make version # Print the version make build stamps
make test # Run tests with race detector make test # Run tests with race detector
make lint # Run golangci-lint in Docker (requires docker) make lint # Run golangci-lint in Docker (requires docker)
make fmt # Format code and Markdown (requires docker) make fmt # Format code and Markdown (requires docker)
@@ -544,13 +545,19 @@ make clean # Remove build artifacts
### Build-Time Variables ### Build-Time Variables
`make build` sets the version with `-ldflags "-X main.Version=..."`, taking it `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 from `VERSION` when given on the command line (`make build VERSION=1.2.3`) or in
command line (`make build VERSION=1.2.3`). The version appears in the startup the environment, otherwise from `git describe --tags --always --dirty`, and
log and in the health check response. `dev` without git metadata. The version appears in the startup log and in the
health check response.
The Docker image has no `.git`, so the `Dockerfile` takes the version as The image takes it the same way, from the `.git` the build context carries, so a
`--build-arg VERSION`. `make docker` passes it; a plain `docker build` passes plain `docker build .` of a clone stamps the commit it was built from; a clone
none, and that image reports `dev`. without tags, such as a shallow one, stamps the short commit. A
`--build-arg VERSION=...` takes precedence; `make docker` passes the version
`git describe` gives on the host. The build fails when the context carries
`.git` and the version comes out empty, `dev` or `unknown`. `.dockerignore` must
list no tracked file: git in the build would see it as deleted and mark the
version `-dirty`.
--- ---
+2
View File
@@ -19,6 +19,8 @@ trial run of the finished image: https://git.eeqj.de/sneak/dnswatcher/issues/149
# Completed Steps # Completed Steps
- 2026-10-02: a plain `docker build .` of a clone stamps its tag or short
commit, not `dev`: the build context now carries `.git` (closes #210).
- 2026-10-01: a certificate within the expiry warning period is warned about on - 2026-10-01: a certificate within the expiry warning period is warned about on
every TLS check, where some checks used to skip it at random (closes #204). every TLS check, where some checks used to skip it at random (closes #204).
- 2026-10-01: a domain's NS set is its delegation from the parent zone's - 2026-10-01: a domain's NS set is its delegation from the parent zone's
+2 -2
View File
@@ -14,8 +14,8 @@ main() {
cd "$ROOT" cd "$ROOT"
# Own line: a failing command substitution inside an argument does # Own line: a failing command substitution inside an argument does
# not trip `set -e`, so the inline form degrades silently to an # not trip `set -e`, so the inline form degrades silently to an
# empty constant. VERSION is computed here because .dockerignore # empty constant. The VERSION build arg takes precedence over what
# excludes .git, so `git describe` in a build stage cannot find it. # the build would derive from the .git in its context.
version="$(git describe --tags --always --dirty 2>/dev/null || true)" version="$(git describe --tags --always --dirty 2>/dev/null || true)"
[ -n "$version" ] || version="unknown" [ -n "$version" ] || version="unknown"
docker build --no-cache-filter=lint,builder \ docker build --no-cache-filter=lint,builder \