Skip to content

fix(docker): retry go mod download and cache Go modules - #703

Merged
Astach merged 3 commits into
mainfrom
ao/qovery-cli-1/docker-mod-download-retry
Aug 31, 2026
Merged

fix(docker): retry go mod download and cache Go modules#703
Astach merged 3 commits into
mainfrom
ao/qovery-cli-1/docker-mod-download-retry

Conversation

@Astach

@Astach Astach commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Why

The container job of the v1.168.5 release (run 33393047477) failed at Dockerfile:12:

#16 [builder 4/6] RUN go mod download
#16 60.34 go: go4.org@v0.0.0-20260112195520-a5071408f32f:
  read "https://proxy.golang.org/go4.org/@v/...mod":
  stream error: stream ID 171; INTERNAL_ERROR; received from peer

A transient HTTP/2 stream reset on the way to the Go module proxy. The tag itself was fine — Build and Release Latest on the same commit were green, and go4.org is a long-standing indirect dependency.

What turned the blip into a failed release is the builder stage, unchanged in this respect since #167 (2023): it re-fetches all 198 modules on every build with no cache and no retry, and one failed request kills the layer, the job, and the image publish. The result was a half-released v1.168.5 — GitHub release, Homebrew, Scoop, AUR and R2 artifacts published, but no Docker image, with latest on ECR/GHCR still pointing at v1.168.4.

What

  • Retry go mod download up to 5 times with linear backoff (5/10/15/20s, ~50s worst case before failing).
  • Mount BuildKit caches for the Go module cache (/go/pkg/mod) and build cache (/root/.cache/go-build), on both the download and build steps.

Notes

  • GOPROXY's direct fallback does not help here: it only applies to 404/410, not to a stream reset, so retrying is the only way to absorb this class of error.
  • The cache mounts are ephemeral per runner, so in CI they only help within a single build — the retry is what protects releases. Making the cache persist across runs would mean moving the workflow to docker/build-push-action with cache-to: type=gha; happy to do that as a follow-up if wanted.
  • Verified locally against public.ecr.aws/r3m4q3r9/pub-mirror-go:1.25.1: image builds and qovery version reports the injected APP_VERSION; retry path exercised with GOPROXY=http://127.0.0.1:1, which logs 5 attempts and exits 1.
  • Not touched, to keep the diff scoped: the FromAsCasing warnings (as vs AS on the two FROM lines) that show up in every build log.

Summary by cubic

Makes the Docker build resilient to transient proxy failures by retrying go mod download and caching Go modules, so an intermittent HTTP/2 stream reset no longer fails the release and leaves no image published.

Bug Fixes

  • Retries go mod download up to 5 times with 5-second linear backoff on transport failures only.
  • Mounts BuildKit caches for the Go module and build caches on the download and build steps.
  • Fails immediately on non-transient errors (checksum mismatches, missing go.sum entries, go.mod parse errors, unknown revisions, malformed module paths, module lookup disabled).

Written for commit f322bd4. Summary will update on new commits.

Review in cubic

Why:
The v1.168.5 release container job failed at `RUN go mod download` with a
transient HTTP/2 stream reset from proxy.golang.org. The Docker build fetched
all 198 modules uncached with no retry, so a single reset broke the release and
left no image published for the tag.

What:
- Retry `go mod download` up to 5 times with linear backoff (5/10/15/20s).
- Mount BuildKit caches for the module cache and the Go build cache.

Notes:
GOPROXY's `direct` fallback does not cover this error class (only 404/410), so
a retry is the only way to absorb it.
@Astach
Astach marked this pull request as ready for review August 31, 2026 12:59

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 1 file

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread Dockerfile
Why:
The retry loop retried every failure, including ones decidable from go.mod and
go.sum alone. A go.sum checksum mismatch can never succeed on retry, so the
build burned 5 download passes and ~50s of backoff before failing, and buried
Go's SECURITY ERROR banner in retry noise.

What:
Capture stderr and short-circuit when it matches a non-transient error class
(checksum mismatch, missing go.sum entry, go.mod parse or version errors).
Everything else stays retried.

Notes:
Exit codes cannot discriminate here: network failures, checksum mismatches and
unknown revisions all exit 1, and -json changes neither the exit code nor the
stderr routing, so stderr matching is the only available signal. The match list
is a denylist rather than an allowlist of retryable errors on purpose: an
unmatched permanent error costs ~50s of CI, while an unmatched transient one
would break a release.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 1 file (changes from recent commits).

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread Dockerfile Outdated
Why:
The fail-fast list missed deterministic errors, so a bad module path or an
unresolvable version still burned 5 download passes and ~50s of backoff. The
list was drawn on the wrong axis: local-vs-remote rather than whether the proxy
actually answered. `unknown revision` is a definitive negative answer, not a
failed round trip, so retrying it cannot help.

What:
Retry only transport failures. Add `unknown revision`, `malformed module path`
and `module lookup disabled` to the non-transient set.

Notes:
Kept as a denylist rather than an allowlist of retryable errors. The failure
that motivated this PR was `stream error: stream ID 171; INTERNAL_ERROR`, a
string no hand-written transient allowlist would plausibly have contained;
missing an entry there breaks a release, while missing one here costs ~50s.
`no matching versions for query` is not reachable from `go mod download` with
pinned versions, which report `errors parsing go.mod` or `unknown revision`.
@Astach
Astach merged commit 75bca3d into main Aug 31, 2026
7 checks passed
@Astach
Astach deleted the ao/qovery-cli-1/docker-mod-download-retry branch August 31, 2026 13:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants