# Lint phase. The linter is invoked directly rather than through `make # lint` or `script/lint`, which are themselves a docker build. # golangci/golangci-lint:v2.14.0 (Debian-based), 2026-09-24 FROM golangci/golangci-lint@sha256:ad862ba6b3798cbe0fd9fd7408d498fd74fbd2623a92406b2fd3898faf0bf98f AS lint WORKDIR /src COPY go.mod go.sum ./ RUN go mod download COPY . . RUN go vet ./... RUN golangci-lint run --config .golangci.yml ./... # The same checks on the code as a macOS build compiles it, which a Linux # build never compiles. Cgo is off, because compiling cgo code for macOS # needs Apple's SDK headers. That leaves out the files built only with cgo # on macOS: the keychain unlocker's calls into the keychain # (keychainunlocker_cgo.go, and keychainunlocker_test.go) and the Secure # Enclave bindings (internal/macse). Nothing on Linux checks those. RUN GOOS=darwin CGO_ENABLED=0 go vet ./... RUN GOOS=darwin CGO_ENABLED=0 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.24.13-trixie, 2026-02-04 FROM golang@sha256:5835f052b784aa39f2fe9070def3568605c8bc3fcd810f10402066348b61e716 AS test ENV CGO_ENABLED=1 WORKDIR /src COPY go.mod go.sum ./ RUN go mod download COPY . . # Go's build cache goes in a cache mount, not the image layer, which would # take seconds longer to export. --no-cache, on every build in script/, # starts the mount empty; -count=1 keeps a build without it from taking # test results from there. RUN --mount=type=cache,id=sneak/secret/go-build-test,target=/root/.cache/go-build \ go test -count=1 -timeout 90s -race -cover ./... || \ { echo "--- Rerunning with -v for details ---"; \ go test -count=1 -timeout 90s -race -v ./...; exit 1; } # Build stage. Nothing is wanted from either phase above; the copies are # what make BuildKit build them first, so this stage cannot run unless # lint and test passed. # golang 1.24.13-alpine, 2026-03-10 FROM golang@sha256:8bee1901f1e530bfb4a7850aa7a479d17ae3a18beb6e09064ed54cfd245b7191 AS builder COPY --from=lint /src/go.sum /dev/null COPY --from=test /src/go.sum /dev/null # script/build compiles with cgo, so it needs a C compiler too. RUN apk add --no-cache gcc musl-dev make git # 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 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; \ make build VERSION="${VERSION:-dev}" # Runtime stage, and the last one # alpine 3.23, 2026-03-10 FROM alpine@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659 RUN apk add --no-cache ca-certificates gnupg RUN adduser -D -s /bin/sh secret COPY --from=builder /src/secret /usr/local/bin/secret RUN chmod +x /usr/local/bin/secret USER secret WORKDIR /home/secret ENTRYPOINT ["secret"]