{"record":{"id":"cd63e3b5321c18d5","repo":"amir20/dozzle","slug":"registry-rate-limited-the-request","errorCode":null,"errorMessage":"registry rate limited the request","messagePattern":"registry rate limited the request","errorType":"error_code","errorClass":"ErrRateLimited","httpStatus":null,"severity":"warning","filePath":"internal/imagecheck/registry.go","lineNumber":35,"sourceCode":"// acceptManifests lists every manifest media type we are willing to receive.\n// Multi-arch images resolve to an index/manifest-list, which is the digest\n// recorded in the local RepoDigests, so those come first.\nvar acceptManifests = strings.Join([]string{\n\t\"application/vnd.oci.image.index.v1+json\",\n\t\"application/vnd.docker.distribution.manifest.list.v2+json\",\n\t\"application/vnd.docker.distribution.manifest.v2+json\",\n\t\"application/vnd.oci.image.manifest.v1+json\",\n}, \",\")\n\nvar (\n\t// ErrAuthRequired means the registry rejected an anonymous request. Dozzle\n\t// has no credential store, so private images are reported rather than\n\t// retried.\n\tErrAuthRequired = errors.New(\"registry requires authentication\")\n\t// ErrNotFound means the tag no longer exists upstream.\n\tErrNotFound = errors.New(\"image not found in registry\")\n\t// ErrRateLimited means the registry asked us to back off.\n\tErrRateLimited = errors.New(\"registry rate limited the request\")\n)\n\ntype cachedToken struct {\n\ttoken     string\n\texpiresAt time.Time\n}\n\n// Registry resolves the current manifest digest for an image reference using\n// HEAD requests, which registries do not count against image pull rate limits.\ntype Registry struct {\n\tclient *http.Client\n\n\tmu     sync.Mutex\n\ttokens map[string]cachedToken\n}\n\nfunc NewRegistry(timeout time.Duration) *Registry {\n\treturn &Registry{","sourceCodeStart":17,"sourceCodeEnd":53,"githubUrl":"https://github.com/amir20/dozzle/blob/d9463cbe21874e44ab79db6fa63e746ca7d22928/internal/imagecheck/registry.go#L17-L53","documentation":"ErrRateLimited means the registry responded with HTTP 429 (TooManyRequests) and asked the client to back off. Dozzle surfaces it as a distinct sentinel so callers can distinguish throttling from real failures.","triggerScenarios":"Digest receives http.StatusTooManyRequests from the registry (registry.go:109), most commonly from Docker Hub's anonymous pull-rate limits when many update checks originate from one IP.","commonSituations":"Docker Hub's per-IP anonymous rate limit (100 pulls/6h) hit on busy hosts or CI; shared NAT/VPN IPs exhausted of quota; aggressive update-check intervals polling a registry that throttles.","solutions":["Wait for the rate window to reset and retry; the check is transient by nature.","Authenticate to the registry (e.g. Docker Hub paid plan or logged-in token) to raise limits — note Dozzle itself has no credential store, so use a mirror/proxy if needed.","Reduce update-check frequency or the number of watched images.","Put a pull-through cache/registry mirror (e.g. a local registry proxy) in front of Docker Hub."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":"func isRateLimited(err error) bool { return errors.Is(err, imagecheck.ErrRateLimited) }","tryCatchPattern":"if errors.Is(err, imagecheck.ErrRateLimited) {\n  // honor Retry-After if present, then retry with exponential backoff\n  time.Sleep(backoff)\n  retry()\n}","preventionTips":["Use a registry mirror or pull-through cache for Docker Hub","Keep update-check intervals modest","Watch fewer images on rate-limited anonymous access","Expect 429 bursts on shared/NAT egress IPs"],"tags":["docker","registry","rate-limit","http-429"],"backgroundTag":"rate-limit-exceeded","analyzedSha":"d9463cbe21874e44ab79db6fa63e746ca7d22928","analyzedAt":"2026-09-07T10:08:55.855Z","contentChangedAt":"2026-09-07T10:08:55.855Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}