check / check (push) Waiting to run
Each `go mod download` in the Dockerfile now runs under a 180-second time limit and is tried up to three times. Without the 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. When all three attempts fail, the step prints "module download failed" and exits 1, so the build stops there and does not look like a test or lint failure. The loop is repeated in each of the three stages because a Dockerfile cannot share shell code between stages. A test checks every download step for the limit, the retries, the message and the final `exit 1`. Judgement call: 180 seconds and three attempts, against a normal download of 13 to 24 seconds. Deviation: REPO_POLICIES.md's model Go Dockerfile still shows a bare `RUN go mod download`. 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 download normally
|
|
# takes under half a minute.
|
|
RUN for attempt in 1 2 3; do \
|
|
timeout 180 go mod download && exit 0; \
|
|
echo "go mod download attempt $attempt failed or ran over 180s" >&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 180 go mod download && exit 0; \
|
|
echo "go mod download attempt $attempt failed or ran over 180s" >&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 180 go mod download && exit 0; \
|
|
echo "go mod download attempt $attempt failed or ran over 180s" >&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"]
|