# Lint-only image: used by script/lint on machines where the docker # daemon is remote (no bind mounts possible) — the repo is COPYed into # the build context and golangci-lint runs as a build step, so a # successful build means a clean lint. # # Two stages on purpose. script/lint builds with --no-cache-filter=lint so # that the lint stage re-executes on every run, including on an unchanged # tree (caching of the lint result is explicitly waived: a cached build # lints nothing). Keeping `go mod download` in a separate `deps` stage # means busting the lint stage does not also re-fetch the module cache # over the network every time. # golangci/golangci-lint:v2.12.2 (Debian-based), 2026-08-07 FROM golangci/golangci-lint:v2.12.2@sha256:5cceeef04e53efe1470638d4b4b4f5ceefd574955ab3941b2d9a68a8c9ad5240 AS deps WORKDIR /src # Copy go mod files first for better layer caching COPY go.mod go.sum ./ RUN go mod download # This stage must stay the one that runs golangci-lint, and its name must # match $stage in script/lint, which passes that single name to both # --target and --no-cache-filter. Renaming here without updating script/lint # fails the build loudly (--target rejects a name that is not in this file), # so a mismatch cannot pass silently — but moving the lint step to another # stage, or adding a stage after this one, would not be caught. Change the # two files together. FROM deps AS lint # Copy source code COPY . . # No `golangci-lint config verify` step here, deliberately (sneak/homoicon # has one). It resolves its JSON schema over a live, unpinned HTTPS call: # an unpinned network input inside the one step whose whole purpose is a # pinned, reproducible gate, and a schema-host outage would show up as a # red build. `golangci-lint run` already fails on a malformed config. RUN golangci-lint run --config .golangci.yml ./...