Derive the image's version from the .git in the build context (closes #366)
check / check (push) Waiting to run

A plain docker build, as upaas runs it, passed no VERSION build arg and
had no .git, so every such image stamped "unknown". .dockerignore now
sends .git and every tracked file (an excluded one would read as
deleted and mark the version -dirty), and the VERSION build arg loses
its "unknown" default, so script/version derives the version inside the
build. A VERSION build arg still takes precedence.

The builder stage installs git and fails when its context carries .git
and the version still comes out "unknown".

Model: opus-5-5
This commit is contained in:
2026-10-01 22:08:02 +00:00
parent 1ac4fe0be4
commit 3502fac119
8 changed files with 67 additions and 51 deletions
+23 -13
View File
@@ -1123,13 +1123,21 @@ build itself.
| Uncommitted changes | the above with a `-dirty` suffix |
| No git metadata | `unknown` |
`unknown` is what a source tarball or a `docker build .` with no
`--build-arg VERSION=...` reports. `.dockerignore` excludes `.git/`, so
the build context carries no git metadata and the image cannot derive
the version itself: `script/docker` (and so `make docker`) resolves it
on the host and passes it in as the `VERSION` build arg. A build that
reports `unknown` is a build nobody told what it was; it is not a
failure, but it cannot be traced back to a commit.
The image derives it the same way, from the `.git` that the build
context carries, so any `docker build .` of a clone stamps the commit it
was built from; a shallow clone of one branch has no tags and stamps the
short SHA. `.dockerignore` must therefore leave out neither `.git` nor
any tracked file, which git in the build would see as deleted, marking
the version `-dirty`. A `VERSION` build arg (`--build-arg VERSION=...`)
takes precedence; `script/docker` (and so `make docker`) passes the one
`script/version` resolves on the host. The image build fails if its
context carries `.git` and the version still comes out `unknown`, which
means git in the build could not read the checkout.
`unknown` is what a source tarball, or a `docker build` with no `.git`
in its context and no `VERSION` build arg, reports. A build that reports
`unknown` is a build nobody told what it was; it is not a failure, but
it cannot be traced back to a commit.
`make version` prints what the current checkout would stamp, and
`make build VERSION=v1.2.3` overrides it. An empty override — from
@@ -3165,8 +3173,9 @@ version is fixed independently of the compiler's:
rebuilds the binary with `CGO_ENABLED=1` and static linking so it
runs on musl. Both builds go through `make build`, the relink adding
its `-extldflags` via `GO_LDFLAGS`, so neither can drop the `-X` that
stamps the version. The version arrives as the `VERSION` build arg,
since the context has no `.git` (see
stamps the version. The version is the `VERSION` build arg if one is
given, otherwise derived from the `.git` in the context, and the
stage fails if a context with `.git` would stamp `unknown` (see
[Version stamping](#version-stamping)).
3. **Runtime stage** (`alpine:3.21`) — copies the static binary and
`deploy/docker-entrypoint.sh`, creates the `/var/lib/webhooker`
@@ -3198,16 +3207,17 @@ A layer cache lets `docker build .` exit 0 in seconds with the lint and
test stages replayed rather than executed, which would make a green
check meaningless. The `check` workflow therefore writes
`.ci-fingerprint` into the build context before building. Its value is
the hash of the last commit that touched the build context, so:
the hash of the last commit that touched anything other than `*.md`,
`LICENSE` and `.editorconfig`, so:
- Any commit that changes code (including a squash merge whose tree
matches an already-built branch) gets a new fingerprint, invalidates
the `COPY . .` layer of both check stages, and really runs
`make fmt-check`, `golangci-lint`, `make test`, and `make build`. A
run that reports success ran them.
- A docs-only commit leaves the fingerprint unchanged — `.dockerignore`
excludes `*.md`, `LICENSE` and `.editorconfig` from the context
anyway — so the image replays from cache and costs seconds.
- A docs-only commit leaves the fingerprint unchanged, but it still
rebuilds in full: the context also carries `.git`, which changes with
every commit (see [Version stamping](#version-stamping)).
The module download layer sits above `COPY . .` and stays cached either
way.