check / check (push) Waiting to run
Each `go mod download` in the Dockerfile now runs under a five-minute time limit and is tried up to three times. Without a limit, a stalled connection to the module proxy held the step, and every build on the host sharing it, until the kernel dropped the connection. Five minutes is well above the three minutes a working download can take on a busy build host. When all three attempts fail, the step prints "module download failed" and exits 1, so the build stops there instead of failing as a test or lint failure. A test checks every download step for the limit, the retries, the message and the final `exit 1`. Model: opus-5-5
109 lines
4.6 KiB
Docker
109 lines
4.6 KiB
Docker
# 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-10-05
|
|
FROM golangci/golangci-lint:v2.14.0@sha256:ad862ba6b3798cbe0fd9fd7408d498fd74fbd2623a92406b2fd3898faf0bf98f AS lint
|
|
WORKDIR /src
|
|
COPY go.mod go.sum ./
|
|
# Without the time limit a stalled connection to the module proxy holds
|
|
# this step, and every build on the host sharing it, until the kernel
|
|
# drops the connection, which can take an hour. The limit is well above
|
|
# the three minutes a working download can take on a busy build host.
|
|
RUN for attempt in 1 2 3; do \
|
|
timeout 300 go mod download && exit 0; \
|
|
echo "go mod download attempt $attempt failed or ran over 300s" >&2; \
|
|
done; \
|
|
echo "module download failed: giving up after 3 attempts" >&2; \
|
|
exit 1
|
|
COPY . .
|
|
# `golangci-lint run` silently ignores an unknown top-level key in
|
|
# .golangci.yml, such as a misspelt `linters:`; `config verify` fails on it.
|
|
RUN golangci-lint config verify --config .golangci.yml
|
|
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.26.1 (Debian trixie), 2026-10-05
|
|
FROM golang:1.26.1@sha256:cd78d88e00afadbedd272f977d375a6247455f3a4b1178f8ae8bbcb201743a8a AS test
|
|
WORKDIR /src
|
|
COPY go.mod go.sum ./
|
|
# Limited and retried as in the lint phase above.
|
|
RUN for attempt in 1 2 3; do \
|
|
timeout 300 go mod download && exit 0; \
|
|
echo "go mod download attempt $attempt failed or ran over 300s" >&2; \
|
|
done; \
|
|
echo "module download failed: giving up after 3 attempts" >&2; \
|
|
exit 1
|
|
COPY . .
|
|
RUN go test -timeout 90s -race -cover ./... || \
|
|
{ echo "--- Rerunning with -v for details ---"; \
|
|
go test -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.26.1-alpine, 2026-03-17
|
|
FROM golang:1.26.1-alpine@sha256:2389ebfa5b7f43eeafbd6be0c3700cc46690ef842ad962f6c5bd6be49ed82039 AS builder
|
|
COPY --from=lint /src/go.sum /dev/null
|
|
COPY --from=test /src/go.sum /dev/null
|
|
RUN apk add --no-cache 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 ./
|
|
# Limited and retried as in the lint phase above.
|
|
RUN for attempt in 1 2 3; do \
|
|
timeout 300 go mod download && exit 0; \
|
|
echo "go mod download attempt $attempt failed or ran over 300s" >&2; \
|
|
done; \
|
|
echo "module download failed: giving up after 3 attempts" >&2; \
|
|
exit 1
|
|
COPY . .
|
|
|
|
# The VERSION build arg when one is given, otherwise
|
|
# `git describe --tags --always` on the .git in the build context. The
|
|
# commit and its date always come from that .git. With .git present, a
|
|
# version that is still empty, dev or unknown, or a commit or date that
|
|
# is unknown, fails the build: git is missing or could not read the
|
|
# checkout, as when .git is a file pointing outside the context. A
|
|
# context without .git, such as a source export, stamps "dev" and an
|
|
# "unknown" commit and date.
|
|
ARG VERSION
|
|
RUN VERSION="${VERSION:-$(git describe --tags --always || echo dev)}"; \
|
|
commit="$(git rev-parse HEAD || echo unknown)"; \
|
|
commit_date="$(git show -s --format=%cs HEAD || echo unknown)"; \
|
|
if [ -e .git ]; then \
|
|
case "$VERSION" in ""|dev|unknown) \
|
|
echo "version is '$VERSION' although .git is present" >&2; \
|
|
exit 1 ;; \
|
|
esac; \
|
|
if [ "$commit" = unknown ] || [ "$commit_date" = unknown ]; then \
|
|
echo "commit is '$commit' and its date '$commit_date'" \
|
|
"although .git is present" >&2; \
|
|
exit 1; \
|
|
fi; \
|
|
fi; \
|
|
globals=sneak.berlin/go/vaultik/internal/globals; \
|
|
CGO_ENABLED=0 go build -trimpath \
|
|
-ldflags="-s -w -X ${globals}.Version=${VERSION} \
|
|
-X ${globals}.Commit=${commit} \
|
|
-X ${globals}.CommitDate=${commit_date}" \
|
|
-o /vaultik ./cmd/vaultik
|
|
|
|
# Runtime stage, and the last one: a plain `docker build .` builds this
|
|
# stage's chain and nothing else.
|
|
# 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"]
|