All checks were successful
check / check (push) Successful in 1m9s
The `(Debian-based)` parenthetical broke the required `# image:vX.Y.Z, YYYY-MM-DD` form and asserted a base change that never happened (v2.12.1 was Debian too); the tag before the digest left three FROM lines in one file using two conventions. Digest unchanged, in both Dockerfile and Dockerfile.lint. The golang and alpine pin comments already matched the required form. script/verify-lint-image-pin parses these two FROM lines to keep them identical and still matches the tagless form; its advice line drops the now-meaningless "tag and digest". With no tag in either reference a tag-only disagreement cannot arise; a tag reintroduced on one side is caught as a plain mismatch.
120 lines
5.2 KiB
Docker
120 lines
5.2 KiB
Docker
# Lint stage — fast feedback on formatting and lint issues
|
|
# golangci/golangci-lint:v2.12.2, 2026-08-07
|
|
FROM golangci/golangci-lint@sha256:5cceeef04e53efe1470638d4b4b4f5ceefd574955ab3941b2d9a68a8c9ad5240 AS lint
|
|
WORKDIR /src
|
|
COPY go.mod go.sum ./
|
|
RUN go mod download
|
|
COPY . .
|
|
|
|
# Cache-buster for the gate layers, and only for them. Docker
|
|
# invalidates COPY only when the copied content changes, so on an
|
|
# unchanged tree the gates below would be served from cache and the
|
|
# build would exit 0 having run nothing. script/cibuild and
|
|
# script/docker pass a fresh CHECK_EPOCH on every invocation.
|
|
#
|
|
# Two properties this depends on. ARG is per-stage, so the build stage
|
|
# below declares it again; one declaration here would leave that
|
|
# stage's gate cacheable. And each gate RUN must reference the value,
|
|
# because BuildKit hashes the expanded command: a declared but
|
|
# unreferenced ARG invalidates nothing.
|
|
#
|
|
# It sits below the dependency layers deliberately. Everything above it
|
|
# (the pinned base image, go mod download) keeps its cache; only the
|
|
# gates go cold.
|
|
ARG CHECK_EPOCH
|
|
|
|
# The linter is invoked directly here, not through `make lint`. That
|
|
# target now runs `docker build -f Dockerfile.lint`, and a docker build
|
|
# cannot run a docker build: routing the gate through make would mean
|
|
# nesting docker inside this image. Same reason `make check` is gone
|
|
# from the build stage below. `make fmt-check` stays as it is — it is a
|
|
# gate, not the aggregate, and it shells out to nothing.
|
|
RUN echo "gate fmt-check, epoch ${CHECK_EPOCH}" && make fmt-check
|
|
|
|
# The FROM above and the one in Dockerfile.lint pin the same linter
|
|
# twice, and nothing else keeps them in sync; this fails the build when
|
|
# they disagree. See the script for why it restates neither pin.
|
|
RUN echo "gate lint-image-pin, epoch ${CHECK_EPOCH}" && \
|
|
script/verify-lint-image-pin
|
|
|
|
# Same config-schema check Dockerfile.lint runs, kept here so this build
|
|
# gates on exactly what script/lint gates on. It validates against a
|
|
# schema the pinned binary embeds, so it needs no network.
|
|
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 ./...
|
|
|
|
# Build stage
|
|
# golang:1.25-alpine, 2026-07-23
|
|
FROM golang@sha256:56961d79ea8129efddcc0b8643fd8a5416b4e6228cfd477e3fd61deb2672c587 AS builder
|
|
|
|
# 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
|
|
# their caches when we drop to it below. $GOPATH/bin is deliberately not
|
|
# on PATH: script/bootstrap no longer `go install`s anything (the linter
|
|
# runs from a pinned image, never from a host install), so nothing lands
|
|
# there and adding it would only widen what this image resolves.
|
|
RUN adduser -D -u 1000 builder
|
|
ENV HOME=/home/builder
|
|
ENV GOPATH=/home/builder/go
|
|
ENV GOCACHE=/home/builder/.cache/go-build
|
|
|
|
WORKDIR /src
|
|
|
|
# No-op file copy whose only purpose is the build-graph edge: it is what
|
|
# makes this stage depend on the lint stage, and so what forces BuildKit
|
|
# to finish fmt-check, the pin guard and lint before compilation and
|
|
# tests start. Remove it and the fail-fast design dies silently — the
|
|
# build stops gating on lint and still exits 0. It replaces a copy of
|
|
# the linter binary itself, which is no longer wanted here: nothing in
|
|
# this stage runs the linter, because `make lint` is now a docker build
|
|
# and a docker build cannot run inside one.
|
|
COPY --from=lint /src/go.sum /dev/null
|
|
|
|
# Install development prerequisites the same way a developer does,
|
|
# rather than duplicating the installs inline. Only script/ and the
|
|
# dependency manifests are copied first, nothing else, so this layer
|
|
# stays cached until the scripts or the dependencies change — bootstrap
|
|
# ends in `go mod download`, which is why there is no separate
|
|
# invocation of it here.
|
|
COPY script/ script/
|
|
COPY go.mod go.sum ./
|
|
RUN script/bootstrap
|
|
|
|
COPY . .
|
|
|
|
# Hand the sources and caches to the unprivileged user, then drop root
|
|
# before running any checks or builds.
|
|
RUN chown -R builder:builder /src /home/builder
|
|
USER builder
|
|
|
|
# Fail the build unless the branch is green. Runs as non-root so the
|
|
# permission-denied test paths are exercised legitimately (root would
|
|
# bypass the chmod(0) the tests rely on).
|
|
#
|
|
# The gates are the individual targets, not `make check`: that aggregate
|
|
# runs `script/lint`, which is now a docker build, and nothing inside an
|
|
# image build may shell out to docker. Lint is not skipped by this — it
|
|
# ran in the lint stage above, which this stage's COPY --from makes a
|
|
# prerequisite. `make`, not the scripts directly, because the Makefile's
|
|
# `export CGO_ENABLED = 0` applies only to what it invokes.
|
|
#
|
|
# Second per-stage declaration of the gate cache-buster; see the lint
|
|
# stage above for why one is not enough. It is placed after USER so the
|
|
# drop to the unprivileged user still happens before the checks run.
|
|
ARG CHECK_EPOCH
|
|
RUN echo "gate test, epoch ${CHECK_EPOCH}" && make test
|
|
RUN echo "gate fmt-check, epoch ${CHECK_EPOCH}" && make fmt-check
|
|
|
|
RUN make build
|
|
|
|
# Runtime stage
|
|
# alpine:3.22, 2026-07-23
|
|
FROM alpine@sha256:14358309a308569c32bdc37e2e0e9694be33a9d99e68afb0f5ff33cc1f695dce
|
|
|
|
COPY --from=builder /src/sfdupes /usr/local/bin/sfdupes
|
|
|
|
ENTRYPOINT ["sfdupes"]
|