# Lint-only image, built by script/lint: the repo is copied into the # pinned golangci-lint image and the linter runs as a build step, so a # successful build is a clean lint. No bind mount, so it works when the # docker daemon is remote. # # It is separate from the main Dockerfile's lint stage because # script/lint must not depend on the rest of that build; the two FROM # lines are kept identical by script/verify-lint-image-pin, run as a # gate below. # golangci/golangci-lint:v2.12.2, 2026-08-07 FROM golangci/golangci-lint@sha256:5cceeef04e53efe1470638d4b4b4f5ceefd574955ab3941b2d9a68a8c9ad5240 WORKDIR /src # Dependency layers first, so they stay cached across lint runs. COPY go.mod go.sum ./ RUN go mod download COPY . . # Cache-buster for the gate layers, and only for them; caching of the # lint run is waived by ruling. On an unchanged tree the gates below # would be served from cache and this build would exit 0 in under a # second having run no linter. script/lint passes a fresh value on every # invocation. # # Each gate RUN must reference the value: BuildKit hashes the expanded # command, so a declared but unreferenced ARG invalidates nothing. Keep # it below the dependency layers so they stay cached. ARG CHECK_EPOCH # Fails the build when the FROM above and the main Dockerfile's lint # stage pin different linter images. RUN echo "gate lint-image-pin, epoch ${CHECK_EPOCH}" && \ script/verify-lint-image-pin # Validates .golangci.yml against golangci-lint's JSON schema, which the # pinned binary embeds: measured under `--network none`, it passes a # valid config and rejects an invalid one. No gate step makes a network # call, but `go mod download` above needs the network on a cold cache. RUN echo "gate config verify, epoch ${CHECK_EPOCH}" && \ golangci-lint config verify --config .golangci.yml RUN echo "gate lint, epoch ${CHECK_EPOCH}" && \ golangci-lint run --config .golangci.yml ./...