# This file has no lint stage, deliberately. # # Linting lives in Dockerfile.lint, built by script/lint, and # script/cibuild builds both. A lint stage here would have to either # shell out to `make lint` -- which is now `docker build`, so # docker-in-docker inside a BuildKit step with no daemon -- or call # golangci-lint directly, which would mean a second, independently # bumpable digest pin for the linter alongside the one in # Dockerfile.lint. Two pins for one tool is the drift that # https://git.eeqj.de/sneak/vaultik/issues/78 was filed over. See # https://git.eeqj.de/sneak/vaultik/issues/113 for the ruling. # # Consequence, stated rather than left to be discovered: script/docker # builds this file only and therefore does not lint. `make fmt-check` # and `make test` still run here, so what a green build of this file # means is "formatted, tested, and it compiles" -- the lint verdict # comes from script/lint or script/cibuild. # Build stage # golang:1.26.1-alpine, 2026-03-17 FROM golang:1.26.1-alpine@sha256:2389ebfa5b7f43eeafbd6be0c3700cc46690ef842ad962f6c5bd6be49ed82039 AS builder # Build tooling: make, plus a C toolchain because `go test -race` needs cgo, # and git, which derives the version below. The sqlite driver is pure Go # (modernc.org/sqlite), so no sqlite library or CLI is required. RUN apk add --no-cache make build-base git WORKDIR /src # Copy go mod files first for better layer caching COPY go.mod go.sum ./ RUN go mod download # Copy source code COPY . . # Run the format check and the tests. # # CHECK_EPOCH must stay immediately above these RUNs. These layers are # keyed on its value, so they are cache-eligible only for a value # already built against this same tree. script/cibuild and script/docker # each pass a fresh value on every invocation, which is what makes their # green mean the checks really executed. # # The value is expanded into each check command rather than left to a # bare declaration, so the cache miss does not depend on BuildKit's # unreferenced-ARG handling staying as it is. It also puts the epoch in # the build log, where a reader can see the layer was keyed fresh. # # A build that passes no CHECK_EPOCH, such as a plain `docker build .`, # keys these layers on the empty string, so rebuilding an unchanged # checkout replays them from cache and runs nothing. Only the scripts' # builds mean the checks executed. # # Everything above this line (apk, go.mod, `go mod download`) is # deliberately outside the busted range and keeps caching. ARG CHECK_EPOCH RUN echo "check epoch: ${CHECK_EPOCH}" && make fmt-check RUN echo "check epoch: ${CHECK_EPOCH}" && make test # Version, commit and build date: the build args when given (script/docker # and script/cibuild pass the ones they compute on the host), otherwise # derived from the .git in the build context. The version is then `git # describe --tags --always`: the tag on a tagged commit, tag-N-gHASH after # one, the short commit when no tag is reachable. A context that carries # .git and still yields no version fails the build; one without .git, as # from a source tarball, stamps "dev" and an "unknown" commit and date. # # These ARGs sit here, after the checks, rather than at the top of the # stage: every commit changes their values, and a value change # invalidates all layers below the ARG. Declared up top they would bust # `go mod download`; here they only rekey this build layer, which the # COPY of the sources above already rebuilds on any change anyway. ARG VERSION ARG COMMIT ARG COMMIT_DATE # Build (pure Go, no CGO required since we use modernc.org/sqlite) RUN version="${VERSION:-$(git describe --tags --always || echo dev)}"; \ if [ -e .git ] && { [ -z "$version" ] || [ "$version" = dev ] || \ [ "$version" = unknown ]; }; then \ echo "the build context carries .git but yields no version" >&2; \ exit 1; \ fi; \ commit="${COMMIT:-$(git rev-parse HEAD || echo unknown)}"; \ commit_date="${COMMIT_DATE:-$(git show -s --format=%cs HEAD || echo unknown)}"; \ CGO_ENABLED=0 go build -ldflags "-X 'sneak.berlin/go/vaultik/internal/globals.Version=${version}' -X 'sneak.berlin/go/vaultik/internal/globals.Commit=${commit}' -X 'sneak.berlin/go/vaultik/internal/globals.CommitDate=${commit_date}'" -o /vaultik ./cmd/vaultik # Runtime stage # alpine:3.21, 2026-02-25 FROM alpine:3.21@sha256:c3f8e73fdb79deaebaa2037150150191b9dcbfba68b4a46d70103204c53f4709 RUN apk add --no-cache ca-certificates # Copy binary from builder COPY --from=builder /vaultik /usr/local/bin/vaultik # Create non-root user RUN adduser -D -H -s /sbin/nologin vaultik USER vaultik ENTRYPOINT ["/usr/local/bin/vaultik"]