All checks were successful
check / check (push) Successful in 2m45s
golangci-lint no longer runs on the host. script/lint builds Dockerfile.lint, which copies the repo into the digest-pinned linter image, so the container holds only this repo and the cross-worktree cache contamination of #106 becomes structurally impossible rather than filtered after the fact. Five workers hit that contamination in one evening, in both directions. Three properties are load-bearing. --no-cache-filter=lint forces the lint stage to re-execute while deps keeps its cache, because a cached build lints nothing in 0.27s and exits 0. script/lint does not trust that flag, since Docker silently ignores an unmatched stage name: it asserts golangci-lint's own summary line appears, so no summary means no lint whatever the exit code says. And both lint steps run --network=none, which enforces rather than assumes that config verify does not fetch its schema — verify is kept, because golangci-lint run silently ignores unrecognized config keys and it is the only thing that catches a typo that disables a setting. Independently reviewed four times. Three passed the behaviour; the remaining rounds were a README merge against #151, whose premise was that the file contains no false statements. Six statements this change falsified were found and corrected across those rounds — the last reviewer re-derived every countable claim against the tree rather than reading for plausibility, and found no seventh. Supersedes #106.
38 lines
1.8 KiB
Docker
38 lines
1.8 KiB
Docker
# Lint-only image, built by script/lint. golangci-lint is never installed on
|
|
# the host: the repo is COPYed into the pinned image and linted as a build
|
|
# step, so a successful build IS a clean lint. This works even when the docker
|
|
# daemon is remote and bind mounts are impossible.
|
|
#
|
|
# script/lint passes --no-cache-filter=lint. Without it an unchanged tree
|
|
# replays the lint stage from cache and the build succeeds in under a second
|
|
# having run no linter at all. Do not drop that flag.
|
|
#
|
|
# The lint steps run with --network=none. `golangci-lint config verify` is
|
|
# documented as fetching its JSON schema over HTTPS, which would make linting
|
|
# depend on an unpinned remote artifact; this pinned image resolves the schema
|
|
# without any network, and --network=none enforces that rather than trusting
|
|
# it. It also proves no linter reaches out at analysis time. If a future image
|
|
# bump makes either step need the network, this build fails loudly instead of
|
|
# quietly acquiring an unpinned dependency.
|
|
|
|
# golangci/golangci-lint:v2.12.2 (Debian-based), 2026-08-07
|
|
# Using Debian-based image because mattn/go-sqlite3 (CGO) does not
|
|
# compile on Alpine musl (off64_t is a glibc type).
|
|
FROM golangci/golangci-lint:v2.12.2@sha256:5cceeef04e53efe1470638d4b4b4f5ceefd574955ab3941b2d9a68a8c9ad5240 AS deps
|
|
|
|
WORKDIR /src
|
|
|
|
# Copy go mod files first for better layer caching. This stage is cacheable;
|
|
# only the lint stage below is forced to re-execute.
|
|
COPY go.mod go.sum ./
|
|
RUN go mod download
|
|
|
|
FROM deps AS lint
|
|
|
|
COPY . .
|
|
|
|
# `run` silently ignores config keys it does not recognize, so a typo would
|
|
# disable a setting without a word. `config verify` is what catches that.
|
|
RUN --network=none golangci-lint config verify --config .golangci.yml
|
|
RUN --network=none golangci-lint run --config .golangci.yml ./...
|