Skip to content

ci: pull Docker Hub test images through the mirror.gcr.io cache - #808

Merged
EricAndrechek merged 2 commits into
mainfrom
ci/test-image-mirror
Oct 12, 2026
Merged

EricAndrechek merged 2 commits into
mainfrom
ci/test-image-mirror

Conversation

@EricAndrechek

Copy link
Copy Markdown
Member

Summary

CI's container jobs no longer pull Docker Hub images under Docker Hub's anonymous limit, and no repository secret is needed. The integration and e2e jobs now run scripts/ci/docker-hub-mirror.sh right after checkout. The script adds https://mirror.gcr.io, Google's cache of Docker Hub, to the runner daemon's registry-mirrors in /etc/docker/daemon.json and keeps the keys already there. Then it sends dockerd a SIGHUP, because registry-mirrors is one of the settings dockerd reloads without a restart. From then on, dockerd tries the mirror first for every Docker Hub pull on that runner and falls back to Docker Hub when the mirror lacks an image or refuses the pull. That covers testcontainers (Ryuk included), the orchestrator and the ClickHouse prefetch.

  • No image name changes. The Go tests, the orchestrator, the compose files and the prefetch keep their Docker Hub names, and the mirror is named in the script alone.
  • A broken mirror costs nothing. The step is continue-on-error: if it fails, the daemon keeps pulling from Docker Hub, as before.
  • Local runs are unchanged. The development guide says how to set the same mirror, or docker login, after a local toomanyrequests.

Images the suites pull

Image Pulled by Registry
clickhouse/clickhouse-server:26.6.3.62 tests/integration, scripts/orchestrator, CI prefetch Docker Hub
redis:8.10.2-alpine internal/cache, tests/integration/shared_cache_test.go, scripts/orchestrator Docker Hub
valkey/valkey:8.1.10-alpine internal/cache Docker Hub
nats:2.14.6-alpine tests/integration (startNATS) Docker Hub
amazon/dynamodb-local:3.3.1 tests/integration Docker Hub
testcontainers/ryuk:0.14.0 testcontainers itself (internal/config.ReaperDefaultImage in v0.44.0), once per test process Docker Hub
docker.dragonflydb.io/dragonflydb/dragonfly:v2.0.0 internal/cache Dragonfly's own registry, so the Docker Hub limit doesn't apply

The compose files (deployments/compose/*.yaml) back make dev, not the tests, and scripts/otel/*.sh run only on a developer's machine.

Why this option (measured 2026-10-10 unless labelled)

  • Rewriting image names to the mirror (testcontainers' own TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX, or a helper of ours): rejected.
    • mirror.gcr.io serves official images only under library/. mirror.gcr.io/library/nats:2.14.6-alpine resolves, but mirror.gcr.io/nats:2.14.6-alpine is no such manifest, and testcontainers' prefix is a plain path.Join(prefix, image).
    • A prefixed pull has no fallback, and mirror.gcr.io can refuse a miss on its own upstream quota. The first lookup of mirror.gcr.io/clickhouse/clickhouse-server:26.6.3.62 returned toomanyrequests: Quota exceeded for quota metric 'Read requests to external upstream hosts per organization per host in the US multi-region', and a retry minutes later succeeded.
    • Through registry-mirrors, dockerd normalizes library/ itself and falls back to Docker Hub on such an error. moby's continueOnError falls back for any registry error code from a mirror endpoint (from the source, not tested).
  • ECR Public (public.ecr.aws/docker/library/<image>): rejected.
    • It mirrors only the Docker Official Images. redis and nats resolve there, but ClickHouse, Valkey, dynamodb-local and Ryuk don't exist under public.ecr.aws/docker/, so each image would need its own path mapping.
    • It isn't a drop-in daemon mirror either. Its repositories are docker/library/<image>, not library/<image>, and dockerd puts a mirror URL's path before /v2/, so no mirror URL maps one onto the other (inferred).
  • Caching the images in the Actions cache: rejected.
    • The six Docker Hub images are 559 MiB compressed for linux/amd64 (ClickHouse 264, dynamodb-local 227, Redis 37, Valkey 16, NATS 10, Ryuk 2), and about 1.7 GB unpacked.
    • The cache sizing policy in .github/workflows/README.md already budgets past the 10 GB cap in its worst case, and the first fill would still pull from Docker Hub.
  • docker login with a token: not built, because it needs a repository secret, which is the maintainer's call. It would raise the fallback path from 100 to 200 pulls per six hours, and it composes with the mirror.

Documented limits:

  • Docker Hub: 100 pulls per six hours per IPv4 address or IPv6 /64 when anonymous, and 200 for an authenticated Personal account. A multi-arch pull counts once per architecture.
  • mirror.gcr.io: Google documents no quota. "Pulling cached images does not count against Docker Hub rate limits", and it caches only frequently requested images. The upstream quota above is what a miss met.
  • ECR Public: 1 unauthenticated pull per second, and 500 GB a month of data for unauthenticated customers.

Not covered

publish-dev.yml, release.yml and goreleaser-validate.yml build images whose stages start from alpine:3 and golang:1.26.9-alpine on Docker Hub. They build through BuildKit's docker-container driver, which doesn't read the daemon's registry-mirrors (inferred). That is about one pull per push to main, and it hasn't been seen failing. Moving those base images is a change to the shipped build recipe, so it is left for a follow-up if wanted.

Overlap with #712

#712 moves the ClickHouse pin into internal/chversion and bumps it to 26.8.15.10 (both versions exist on the mirror). This PR changes no image reference. Against #712's head, git merge-tree conflicts in the same files (.github/actions/setup-env/action.yml, .github/workflows/{README.md,ci.yml}) and with the same number of conflict regions as #712 already has against main; one line of this PR's README addition sits inside one of those README regions, so whichever lands second resolves it there. None of this PR's ci.yml lines fall inside a conflict region.

Test plan

  • The script against a real dockerd (measured): a throwaway docker:28.0.4-dind, the same engine version as the ubuntu-24.04 runner image (20261004.327.1), on overlay2.
    • Run as root, and through sudo as uid 1000.
    • An existing daemon.json (exec-opts, max-concurrent-downloads) kept, and a second run is a no-op.
    • /etc/docker created when it is missing.
    • docker info then lists https://mirror.gcr.io/.
  • The suites inside that daemon (measured): test binaries built for linux/arm64 with -tags integration (no -race) all pass.
    • internal/cache TestRedis_Conformance (Redis, Valkey, Dragonfly, Redis Cluster).
    • In tests/integration: TestCoordNATS_ReplicasShareTheShards, TestSharedCache_RedisDownQueriesBypass, TestIngest_FlowsToClickHouseWithoutDLQ and TestDynamoDBDedupe_TwoInstancesShareSeenIDs, with TestMain's ClickHouse and dynamodb-local.
    • The dockerd debug log shows Trying to pull <image> from https://mirror.gcr.io/ for each of the six Docker Hub images, with no Attempting next endpoint and no registry-1.docker.io. Dragonfly came straight from docker.dragonflydb.io.
  • Fallback (measured): with the mirror set to an unresolvable host, the daemon logged Attempting next endpoint for pull after error and pulled nats:2.14.6-alpine from registry-1.docker.io.
  • make lint-sh lint-gha lint-md lint-prose
  • make ci
  • CI (inferred until a run shows it): each container job's "Pull Docker Hub images through a mirror" step logs docker-hub-mirror: Docker Hub pulls try https://mirror.gcr.io first, and toomanyrequests stops appearing in the integration and e2e logs.

Related Issues

Closes #805. Part of #740.

🤖 Generated with Claude Code

The integration and e2e jobs pulled every Docker Hub image anonymously
(ClickHouse, Redis, Valkey, NATS, dynamodb-local and testcontainers'
Ryuk) from hosted runners that share IP addresses, so a job could fail
with toomanyrequests before any test ran.

Each container job now runs scripts/ci/docker-hub-mirror.sh first. It
adds mirror.gcr.io to the runner daemon's registry-mirrors, keeping the
keys already there, and reloads dockerd with SIGHUP. dockerd tries the
mirror first and falls back to Docker Hub, so no image name changes, no
secret is needed, and the step is continue-on-error: a failure leaves
the daemon pulling from Docker Hub as before.

Rewriting image names to the mirror was rejected: mirror.gcr.io serves
official images only under library/ (mirror.gcr.io/nats:2.14.6-alpine
is not found), and a prefixed pull has no fallback when the mirror's
upstream quota refuses a miss. The Actions cache was rejected at about
560 MiB compressed for the six images.

Closes #805.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 5debd014-4841-468e-b9de-a20fdeaa71be


📥 Commits

Reviewing files that changed from the base of the PR and between 6d8729c and 927fe04.



📒 Files selected for processing (2)
  • .github/workflows/README.md
  • CHANGELOG.md


Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.



📜 Recent review details
🧰 Additional context used
📚 Code guidelines (2)
AGENTS.md — auto-discovered
CONTRIBUTING.md — auto-discovered

📓 Path-based instructions (4)
Source excerpt: **WH001 applies to every tracked Markdown file, with no carve-out** — `AGENTS.md`, `CHANGELOG.md`, `.github/` CI docs and `.claude/` agent prompts included.

📄 CodeRabbit inference engine (AGENTS.md)

Files:

  • CHANGELOG.md

Source excerpt: Update the documentation and `CHANGELOG.md` (under `## Unreleased`) that your change affects.

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Files:

  • CHANGELOG.md

Source excerpt: **Never hard-wrap prose.

📄 CodeRabbit inference engine (AGENTS.md)

Files:

  • CHANGELOG.md

Source excerpt: **Docs prose**: never hard-wrap Markdown — one paragraph is one line.

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Files:

  • CHANGELOG.md

🪛 LanguageTool
CHANGELOG.md

[typographical] ~136-~136: Consider using an em dash in dialogues and enumerations.
Context: - **CI's container jobs pull their Docker...

(DASH_RULE)


[uncategorized] ~136-~136: The official name of this software platform is spelled with a capital “H”.
Context: ...scripts/ci/docker-hub-mirror.sh(new),.github/workflows/{ci.yml,README.md}, docs/sr...

(GITHUB)




🔇 Additional comments (2)
.github/workflows/README.md (1)

179-179: LGTM!


CHANGELOG.md (1)

136-136: LGTM!





📝 Summary

Summary by CodeRabbit

  • Bug Fixes

    • Reduced Docker Hub rate-limit issues in CI integration and end-to-end tests by trying a registry mirror first, with fallback to Docker Hub when needed.
  • Documentation

    • Added guidance for understanding CI mirror setup and addressing Docker Hub pull rate limits during local test runs.
📝 Summary

Walkthrough

The CI integration and E2E jobs now attempt to configure a Docker Hub registry mirror before their first image pull. A new script updates and verifies the Docker daemon configuration. Documentation describes CI mirror behavior, troubleshooting, and local options for Docker Hub pull-rate limits.

Changes

CI Docker Hub mirror

Layer / File(s) Summary
Configure and verify the Docker mirror
scripts/ci/docker-hub-mirror.sh
The script places the selected mirror first in the daemon’s registry list, retains other entries, signals dockerd, and checks whether Docker reports the mirror.
Run mirror setup in CI
.github/workflows/ci.yml, .github/workflows/README.md, CHANGELOG.md, docs/src/content/docs/development.md
The integration and E2E jobs run the script before image pulls and continue if it fails. The documentation describes fallback behavior, troubleshooting, and local options for Docker Hub pull limits.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix · Severity of issue fixed: Medium

Suggested reviewers: taitelee



Merge Risk: ⚪ Minimal · up to 927fe

The integration and e2e jobs attempt mirror setup, while the documentation notes that setup does not guarantee where an image is served from. Coverage does not pull container images. No actionable merge-blocking risk is established; the hosted runner’s exact Docker Engine version is not pinned.

Pre-merge checks | Passed 3 | Failed 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check Warning #805 directly covers the integration, e2e, and coverage jobs. The PR configures the mirror before pulls only in the integration and e2e jobs. The reviewed summary shows no mirror setup for the coverag… Configure the Docker daemon mirror, or an equivalent pull-limit mitigation, before the first Docker Hub pull in the coverage job. Then validate the affected jobs.
Docstring Coverage Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 1 files. (2 skipped: 2 … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Out of Scope Changes check Passed The workflow changes and scripts/ci/docker-hub-mirror.sh directly address #805. The workflow documentation, changelog, and development guide document the same mitigation and its fallback behavior. N…
Title check Passed The title clearly and concisely describes the primary CI change: routing Docker Hub test image pulls through the mirror.gcr.io cache.
Description check Passed The description is directly related to the changeset and explains the mirror script, workflow integration, fallback behavior, scope, and validation.

Full details: Linked Issues check

Explanation

#805 directly covers the integration, e2e, and coverage jobs. The PR configures the mirror before pulls only in the integration and e2e jobs. The reviewed summary shows no mirror setup for the coverage job. The script preserves daemon settings, reloads dockerd, and verifies the configured mirror. These changes support two affected jobs, but they do not address the coverage failure identified by #805.


Full details: Docstring Coverage

Explanation

Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 1 files. (2 skipped: 2 unsupported.)


  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR


✨ Simplify code
  • Commit to this branch
  • Create a new PR



  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added documentation Improvements or additions to documentation github_actions Pull requests that update GitHub Actions code area/docs Documentation, site/, README area/infra CI, build, deploy, Docker, release labels Oct 10, 2026
@github-actions

github-actions Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

📚 Docs preview is live → https://6d963dfd-wavehouse-docs.wave-rf.workers.dev

  • Commit — 927fe04: docs(ci): say what the mirror step proves and qualify the rate-limit claim
  • Author — @EricAndrechek, Claude Opus 5.5
  • Committed — 2026-10-10 10:24 (UTC-04:00)
  • Deployed — 2026-10-10 15:20 EDT

@github-code-quality

github-code-quality Bot commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Code Coverage Overview

Languages: Go

Go

The overall line coverage in commit 927fe04 in the ci/test-image-mirror branch remains at 93%, unchanged from commit c01b912 in the main branch.


Updated October 10, 2026 19:23 UTC

@EricAndrechek

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Oct 10, 2026 •

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@EricAndrechek

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Oct 10, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: b95bef4c-acff-4e99-aad2-ec57fbb4ef90
📥 Commits

Reviewing files that changed from the base of the PR and between c01b912 and 6d8729c.

📒 Files selected for processing (5)
  • .github/workflows/README.md
  • .github/workflows/ci.yml
  • CHANGELOG.md
  • docs/src/content/docs/development.md
  • scripts/ci/docker-hub-mirror.sh

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

📜 Review details
🧰 Additional context used
📚 Code guidelines (3)
.github/workflows/README.md — auto-discovered
AGENTS.md — auto-discovered
docs/src/content/docs/development.md — auto-discovered
📓 Path-based instructions (3)
Source excerpt: How a `merge_group` run flows through the DAG: the classifier treats it like a push (full suite — the queue never skips code checks; `docs` still gates docs-build), while `title` (already validated on the PR), `docs-preview`...

📄 CodeRabbit inference engine (.github/workflows/README.md)

Files:

  • .github/workflows/ci.yml
Source excerpt: **WH001 applies to every tracked Markdown file, with no carve-out** — `AGENTS.md`, `CHANGELOG.md`, `.github/` CI docs and `.claude/` agent prompts included.

📄 CodeRabbit inference engine (AGENTS.md)

Files:

  • docs/src/content/docs/development.md
  • CHANGELOG.md
Source excerpt: Run `make fix` to apply them.

📄 CodeRabbit inference engine (docs/src/content/docs/development.md)

Files:

  • docs/src/content/docs/development.md
  • CHANGELOG.md
🧠 Learnings (1)
📚 Learning: 2026-06-10T15:01:09.027Z
Learnt from: EricAndrechek
Repo: Wave-RF/WaveHouse PR: 312
File: docs/src/content/docs/development.md:0-0
Timestamp: 2026-06-10T15:01:09.027Z
Learning: In this repo’s Markdown review (all .md files), do not flag capitalization/style issues for literal paths starting with ".github/" (or any substring that is a path beginning with ".github/"). Treat ".github" as the correct lowercase dotfile directory name, even when it appears inside prose or code spans; automated checks such as LanguageTool’s "(GITHUB)" rule commonly produce false positives for this literal filesystem path.

Applied to files:

  • docs/src/content/docs/development.md
  • CHANGELOG.md
🪛 ast-grep (0.45.3)
scripts/ci/docker-hub-mirror.sh

[error] 26-26: sudo is invoked with a user-controlled variable as the command to run (e.g. sudo $cmd, sudo "$cmd", sudo $(...)) or via a shell evaluating a variable (sudo bash -c "$var", sudo sh -c "$cmd"). Because sudo runs as root, any attacker influence over that value (arguments, environment, file contents, network output) becomes root-level command injection. Never build the privileged command from dynamic data: run a fixed, hard-coded program with the variable passed as a quoted argument (sudo /usr/bin/prog -- "$arg"), use an array for the argument list, or restrict the input to a validated allowlist before escalating privileges.
Context: sudo "$@"
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(sudo-unquoted-variable-bash)

🪛 LanguageTool
docs/src/content/docs/development.md

[uncategorized] ~370-~370: The official name of this software platform is spelled with a capital “H”.
Context: ...ker Hub as the fallback (invariant 8 of [.github/workflows/README.md](https://github.co...

(GITHUB)

.github/workflows/README.md

[style] ~57-~57: Since ownership is already implied, this phrasing may be redundant.
Context: ...tainers' own Ryuk; Dragonfly comes from its own registry), so jobs failed with `toomany...

(PRP_OWN)

CHANGELOG.md

[typographical] ~136-~136: Consider using an em dash in dialogues and enumerations.
Context: - **CI's container jobs pull their Docker...

(DASH_RULE)


[uncategorized] ~136-~136: The official name of this software platform is spelled with a capital “H”.
Context: ...scripts/ci/docker-hub-mirror.sh(new),.github/workflows/{ci.yml,README.md}, docs/sr...

(GITHUB)

🔇 Additional comments (4)
scripts/ci/docker-hub-mirror.sh (1)

20-46: LGTM!

.github/workflows/ci.yml (1)

255-257: LGTM!

Also applies to: 307-309

.github/workflows/README.md (1)

57-58: LGTM!

docs/src/content/docs/development.md (1)

370-370: LGTM!

Comment thread .github/workflows/README.md Outdated
Comment thread CHANGELOG.md Outdated
@github-project-automation github-project-automation Bot moved this from Backlog to In review in WaveHouse Task Board Oct 10, 2026
…claim

A passing mirror step shows the daemon was configured, not where a pull
was served from, and pulls that fall back to Docker Hub still count
against its anonymous limit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@EricAndrechek

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Oct 10, 2026 •

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@EricAndrechek

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Oct 10, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@EricAndrechek
EricAndrechek marked this pull request as ready for review October 10, 2026 19:18
@EricAndrechek
EricAndrechek requested review from a team and taitelee October 10, 2026 19:18
@EricAndrechek
EricAndrechek merged commit 5317487 into main Oct 12, 2026
@EricAndrechek
EricAndrechek deleted the ci/test-image-mirror branch October 12, 2026 15:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/docs Documentation, site/, README area/infra CI, build, deploy, Docker, release documentation Improvements or additions to documentation github_actions Pull requests that update GitHub Actions code

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

ci: Docker Hub anonymous pull rate limit fails integration and e2e jobs on hosted runners

1 participant