# Lint phase. The linter is invoked directly rather than through `make # lint` or `script/lint`, which are themselves a docker build and would # recurse into a daemon that does not exist in a build step. # golangci/golangci-lint:v2.14.0, 2026-09-24 FROM golangci/golangci-lint@sha256:ad862ba6b3798cbe0fd9fd7408d498fd74fbd2623a92406b2fd3898faf0bf98f AS lint WORKDIR /src COPY go.mod go.sum ./ RUN go mod download COPY . . RUN golangci-lint run --config .golangci.yml ./... # Test phase. -race needs cgo and so a C compiler, which the Debian Go # image ships and the alpine one does not. # golang:1.27.1, 2026-10-06 FROM golang@sha256:1e93e00a31255c07e9a34c4207f3006e1501730c5323697cee7dfb827fdae44c AS test WORKDIR /src COPY go.mod go.sum ./ RUN go mod download COPY . . RUN go test -timeout 90s -race -cover ./... || \ { echo "--- Rerunning with -v for details ---"; \ go test -timeout 90s -race -v ./...; exit 1; } # Vulnerability check, built only by script/vulncheck (make vulncheck). # No stage depends on it, so the image build does not run it. # golang:1.27.1, 2026-10-06 FROM golang@sha256:1e93e00a31255c07e9a34c4207f3006e1501730c5323697cee7dfb827fdae44c AS vulncheck # govulncheck v1.8.0, pinned to the commit its release tag names. RUN go install golang.org/x/vuln/cmd/govulncheck@709015412431dd2b5b28a53c06c70bc02d49074c WORKDIR /src COPY go.mod go.sum ./ RUN go mod download COPY . . RUN govulncheck ./... # Build stage. Nothing is wanted from the lint or test phase; the copies # are what make BuildKit build them first, so this stage cannot run # unless lint and test passed. The Debian Go image ships git, which the # version step below needs. # golang:1.27.1, 2026-10-06 FROM golang@sha256:1e93e00a31255c07e9a34c4207f3006e1501730c5323697cee7dfb827fdae44c AS builder COPY --from=lint /src/go.sum /dev/null COPY --from=test /src/go.sum /dev/null # A tar-stream context keeps the sender's file owners, which git refuses. RUN git config --system --add safe.directory /src WORKDIR /src COPY go.mod go.sum ./ RUN go mod download COPY . . # The revision `mfer version` prints, stamped into main.Gitrev: the # VERSION build arg when one is given, otherwise `git describe --tags # --always` on the .git in the build context. With .git present, a # version that is still empty, dev or unknown fails the build: git is # missing or could not read the checkout. ARG VERSION RUN VERSION="${VERSION:-$(git describe --tags --always)}"; \ if [ -e .git ]; then \ case "$VERSION" in ""|dev|unknown) \ echo "version is '$VERSION' although .git is present" >&2; \ exit 1 ;; \ esac; \ fi; \ CGO_ENABLED=0 go build -trimpath \ -ldflags="-s -w -X main.Gitrev=${VERSION}" \ -o /mfer ./cmd/mfer/ # Fail unless /mfer is statically linked: scratch has no C library to run it. RUN ldd /mfer 2>&1 | grep -q 'not a dynamic executable' # Runtime stage, and the last one. FROM scratch # scratch has no CA certificates; fetch needs them to verify HTTPS servers. COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ COPY --from=builder /mfer /mfer ENTRYPOINT ["/mfer"]