Keep the module cache out of the build stage's chown (closes #43)
check / check (push) Successful in 1m5s

The build stage handed /src and the whole Go module cache to the
unprivileged user with chown -R, in a layer that re-ran on every source
change. On this host that step took from about 80 s to over ten minutes,
depending on load.

The module cache now sits at /go/pkg/mod and stays root's: bootstrap
fills it as root. The sources are copied with --chown. A small chown,
cached with bootstrap, hands builder the /src directory itself, the
module cache's cache/download directory (where make build saves its
lookup of this module's own version), and the telemetry files root's go
commands left in its home. Tests and the build still run as builder.

Model: opus-5-5
This commit is contained in:
2026-10-04 04:38:37 +00:00
parent 9abf81535a
commit 49e034f295
2 changed files with 30 additions and 8 deletions
+26 -8
View File
@@ -51,14 +51,21 @@ RUN echo "gate lint, epoch ${CHECK_EPOCH}" && \
FROM golang@sha256:56961d79ea8129efddcc0b8643fd8a5416b4e6228cfd477e3fd61deb2672c587 AS builder FROM golang@sha256:56961d79ea8129efddcc0b8643fd8a5416b4e6228cfd477e3fd61deb2672c587 AS builder
# We never build or run as root. Create an unprivileged user and point # We never build or run as root. Create an unprivileged user and point
# HOME and the Go caches at its home so go build and go test can write # HOME and the build cache at its home so go build and go test can write
# their caches when we drop to it below. $GOPATH/bin is deliberately not # it when we drop to it below. $GOPATH/bin is deliberately not on PATH:
# on PATH: script/bootstrap no longer `go install`s anything (the linter # script/bootstrap no longer `go install`s anything (the linter runs
# runs from a pinned image, never from a host install), so nothing lands # from a pinned image, never from a host install), so nothing lands
# there and adding it would only widen what this image resolves. # there and adding it would only widen what this image resolves.
#
# The module cache is kept outside that home, at the base image's
# default /go/pkg/mod, and belongs to root: script/bootstrap fills it as
# root. Do not move it into the home and hand it over with `chown -R`:
# that walks every file in it, which took from about 80 s to over ten
# minutes on a shared host, depending on load.
RUN adduser -D -u 1000 builder RUN adduser -D -u 1000 builder
ENV HOME=/home/builder ENV HOME=/home/builder
ENV GOPATH=/home/builder/go ENV GOPATH=/home/builder/go
ENV GOMODCACHE=/go/pkg/mod
ENV GOCACHE=/home/builder/.cache/go-build ENV GOCACHE=/home/builder/.cache/go-build
WORKDIR /src WORKDIR /src
@@ -83,11 +90,22 @@ COPY script/ script/
COPY go.mod go.sum ./ COPY go.mod go.sum ./
RUN script/bootstrap RUN script/bootstrap
COPY . . # Hand builder only what it writes to, without walking the module cache.
# This layer stays cached with bootstrap.
# - /src itself: make build writes the binary into it, and git refuses
# a repository whose top directory belongs to another user.
# - the module cache's cache/download directory itself, not what is in
# it: Go only reads the downloaded modules, but make build saves its
# lookup of this module's own version from git there, in a new
# directory named after the module path.
# - builder's home: the go commands bootstrap ran as root left Go's
# telemetry files there, a few small files.
RUN chown builder:builder /src /go/pkg/mod/cache/download && \
chown -R builder:builder /home/builder
# Hand the sources and caches to the unprivileged user, then drop root # The sources are handed to builder as they are copied, so no layer has
# before running any checks or builds. # to walk them. Then drop root before running any checks or builds.
RUN chown -R builder:builder /src /home/builder COPY --chown=builder:builder . .
USER builder USER builder
# Fail the build unless the branch is green. Runs as non-root so the # Fail the build unless the branch is green. Runs as non-root so the
+4
View File
@@ -29,6 +29,10 @@
# Completed Steps # Completed Steps
- the `Dockerfile` build stage keeps the Go module cache out of `builder`'s
home and copies the sources with `--chown`, so no `chown -R` walks them
(2026-10-04, https://git.eeqj.de/sneak/sfdupes/issues/43)
- progress prints at once on a non-terminal, uses a real terminal test, - progress prints at once on a non-terminal, uses a real terminal test,
and prints warnings through a spinner instead of racing its redraw and prints warnings through a spinner instead of racing its redraw
(2026-10-03, https://git.eeqj.de/sneak/sfdupes/issues/13) (2026-10-03, https://git.eeqj.de/sneak/sfdupes/issues/13)