golangci-lint is no longer invoked on the host anywhere in the repo.
Dockerfile.lint pins golangci/golangci-lint:v2.12.2 by digest and runs
the linter as a build step, so a successful build IS a clean lint, and
`make lint` becomes a thin shim over script/lint. This removes the host
linter install that produced a false green here, where a branch that was
genuinely red with a goconst finding reported "0 issues" off the shared
host cache; a container per run has its own cache and lock.
Two deliberate divergences from the sneak/homoicon reference shape:
- Two stages rather than one. A cached `deps` stage holds
`go mod download`, then `FROM deps AS lint` carries the source copy
and the lint run, and script/lint builds with
`--no-cache-filter=lint`. Caching of the lint result is explicitly
waived (a cached build lints nothing), and splitting the stages means
busting the lint layer does not re-fetch the module cache over the
network on every run.
- No `golangci-lint config verify` step. It resolves its JSON schema
over a live, unpinned HTTPS call: an unpinned network input inside
the one step whose purpose is a pinned, reproducible gate, and a
schema-host outage would surface as a red build. `golangci-lint run`
already fails on a malformed config. The reason is recorded in a
comment in Dockerfile.lint.
.dockerignore excludes .git only; the lint reads the Go sources,
go.mod/go.sum and .golangci.yml, none of which come from there.
The TODO.md scaffold-exemption note is narrowed rather than dropped:
Dockerfile.lint and script/lint are now permitted and required, while CI
config, REPO_POLICIES.md, an application Dockerfile and any other
script/ entrypoint still are not.
Verified, since a green docker build is the classic false green: two
consecutive script/lint runs on an unchanged tree each showed the
`golangci-lint run` layer executing (9.8s and 7.9s, both "0 issues.")
while the deps layers reported CACHED; a deliberate indent-error-flow
violation failed the build naming that finding and the unused one, and a
revert went clean again. `make check` green.
33 lines
1.4 KiB
Docker
33 lines
1.4 KiB
Docker
# 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
|
|
|
|
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 ./...
|