Go build cache mounts in the canonical Go Dockerfile, with -count=1 in its test phase #112

Closed
opened 2026-10-06 04:28:25 +02:00 by clawbot · 1 comment
Collaborator

Moved here from sneak/bsfirehose#40: the steps it would change are those of the canonical Go Dockerfile in REPO_POLICIES.md, so making the change in one repository would make that repository diverge from it.

Problem

On bsfirehose next (26a707f, after sneak/bsfirehose#67 re-vendored the canonical files), two limits in REPO_POLICIES.md are broken: script/cibuild took 454s against the 5-minute limit, and make test took 158s against the 60-second cap. Every step that compiles starts from an empty Go build cache, so the lint, test and build steps each compile the whole dependency graph from source, the sqlite driver's C code included, and script/cibuild runs lint and test twice. Measured on the shared build host at a load of about 100–150 on 48 cores.

Proposed change

  • Add --mount=type=cache,target=/root/.cache/go-build to the lint, test and build RUN steps of the canonical Go Dockerfile. The mount keeps compiled packages, not step results: --no-cache still re-runs every step. Go keys each cache entry on the source, the flags and the toolchain, so the mount changes how long a step takes, not what it builds. Keep the default sharing=shared: Go's cache is safe for concurrent use, and sharing=locked would make every build on a host wait for the others. Mount Go's build cache only, never golangci-lint's own cache (#30).
  • Add -count=1 to both go test invocations of the test phase, and rewrite the paragraph that says the test phase needs none: with the mount, the phase does hold stored results. With a warm mount and today's flags, the test step took 3.8s and printed every tested package as (cached); no test ran.

Measurements

A throwaway build ran each step twice in a row on one mount:

step empty cache warm mount
test (-count=1) 127s 8s
lint 131s 15s
build 109s not measured

Question for the owner

The mount saves time only where the builder keeps it between builds, and on the shared build host it does not. Three back-to-back make test builds each got a newly created, empty mount, and inside one script/cibuild the second lint and test steps were as slow as the first. With the change applied locally, make test took 99–174s and script/cibuild 503s, no better than without it. The builder runs with default settings (there is no daemon.json) and its build cache holds 327 GB, so its own cleanup most likely removes a mount that has been used once. Meeting either limit on this host therefore also needs its builder set to keep cache mounts, which is host configuration and not a repository file. Should the build host be configured that way? The reading taken here: propose the policy change regardless, since it costs nothing where the mount is dropped.

Definition of done

  • The canonical Go Dockerfile carries the three mounts and -count=1 on both test invocations, and the policy text says why -count=1 is there.
  • A planted failing test and a planted lint finding still fail script/cibuild with a warm mount.
  • sneak/bsfirehose#40 closes once bsfirehose has re-vendored this and both limits hold there.

Model: opus-5-5

Moved here from https://git.eeqj.de/sneak/bsfirehose/issues/40: the steps it would change are those of the canonical Go `Dockerfile` in `REPO_POLICIES.md`, so making the change in one repository would make that repository diverge from it. ## Problem On bsfirehose `next` (`26a707f`, after https://git.eeqj.de/sneak/bsfirehose/issues/67 re-vendored the canonical files), two limits in `REPO_POLICIES.md` are broken: `script/cibuild` took 454s against the 5-minute limit, and `make test` took 158s against the 60-second cap. Every step that compiles starts from an empty Go build cache, so the lint, test and build steps each compile the whole dependency graph from source, the sqlite driver's C code included, and `script/cibuild` runs lint and test twice. Measured on the shared build host at a load of about 100–150 on 48 cores. ## Proposed change - Add `--mount=type=cache,target=/root/.cache/go-build` to the lint, test and build `RUN` steps of the canonical Go `Dockerfile`. The mount keeps compiled packages, not step results: `--no-cache` still re-runs every step. Go keys each cache entry on the source, the flags and the toolchain, so the mount changes how long a step takes, not what it builds. Keep the default `sharing=shared`: Go's cache is safe for concurrent use, and `sharing=locked` would make every build on a host wait for the others. Mount Go's build cache only, never golangci-lint's own cache (https://git.eeqj.de/sneak/prompts/issues/30). - Add `-count=1` to both `go test` invocations of the test phase, and rewrite the paragraph that says the test phase needs none: with the mount, the phase does hold stored results. With a warm mount and today's flags, the test step took 3.8s and printed every tested package as `(cached)`; no test ran. ## Measurements A throwaway build ran each step twice in a row on one mount: | step | empty cache | warm mount | | ----------------- | ----------- | ------------ | | test (`-count=1`) | 127s | 8s | | lint | 131s | 15s | | build | 109s | not measured | ## Question for the owner The mount saves time only where the builder keeps it between builds, and on the shared build host it does not. Three back-to-back `make test` builds each got a newly created, empty mount, and inside one `script/cibuild` the second lint and test steps were as slow as the first. With the change applied locally, `make test` took 99–174s and `script/cibuild` 503s, no better than without it. The builder runs with default settings (there is no `daemon.json`) and its build cache holds 327 GB, so its own cleanup most likely removes a mount that has been used once. Meeting either limit on this host therefore also needs its builder set to keep cache mounts, which is host configuration and not a repository file. Should the build host be configured that way? The reading taken here: propose the policy change regardless, since it costs nothing where the mount is dropped. ## Definition of done - The canonical Go `Dockerfile` carries the three mounts and `-count=1` on both test invocations, and the policy text says why `-count=1` is there. - A planted failing test and a planted lint finding still fail `script/cibuild` with a warm mount. - https://git.eeqj.de/sneak/bsfirehose/issues/40 closes once bsfirehose has re-vendored this and both limits hold there. Model: opus-5-5
Author
Collaborator

Closing: the record already decides this.

  • sneak ruled on #40 (#40 (comment)) that linting and testing run in Docker "and you can simply disable all caching". A Go build cache mount is caching kept between builds.
  • It also cannot work under the canonical scripts: on this host's Docker, --no-cache gives every BuildKit cache mount a fresh, empty directory (found for #87). That is why each of the measured builds above got a new, empty mount, so the builder's cleanup is not the cause and configuring the host would not change it.

What remains is that uncached builds break the time limits. That is now the owner question on #113, with bsfirehose's figures from this issue added there. sneak/bsfirehose#40 waits on that answer.

Model: opus-5-5

Closing: the record already decides this. - sneak ruled on https://git.eeqj.de/sneak/prompts/issues/40 (https://git.eeqj.de/sneak/prompts/issues/40#issuecomment-54891) that linting and testing run in Docker "and you can simply disable all caching". A Go build cache mount is caching kept between builds. - It also cannot work under the canonical scripts: on this host's Docker, `--no-cache` gives every BuildKit cache mount a fresh, empty directory (found for https://git.eeqj.de/sneak/prompts/pulls/87). That is why each of the measured builds above got a new, empty mount, so the builder's cleanup is not the cause and configuring the host would not change it. What remains is that uncached builds break the time limits. That is now the owner question on https://git.eeqj.de/sneak/prompts/issues/113, with bsfirehose's figures from this issue added there. https://git.eeqj.de/sneak/bsfirehose/issues/40 waits on that answer. Model: opus-5-5
clawbot self-assigned this 2026-10-06 04:33:48 +02:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/prompts#112