{"record":{"id":"1d85113e0a388ef8","repo":"juanfont/headscale","slug":"pulling-image-s-w","errorCode":null,"errorMessage":"pulling image %s: %w","messagePattern":"pulling image (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/hi/docker.go","lineNumber":560,"sourceCode":"\tif verbose {\n\t\tlog.Printf(\"Image %s not found locally, pulling...\", imageName)\n\t}\n\n\tregistryAuth, err := dockertestutil.RegistryAuth()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"resolving registry auth: %w\", err)\n\t}\n\n\t_, err = backoff.Retry(\n\t\tctx,\n\t\tfunc() (struct{}, error) {\n\t\t\treader, pullErr := cli.ImagePull(ctx, imageName, image.PullOptions{RegistryAuth: registryAuth})\n\t\t\tif pullErr != nil {\n\t\t\t\tif isPermanentDockerPullError(pullErr) {\n\t\t\t\t\treturn struct{}{}, backoff.Permanent(pullErr)\n\t\t\t\t}\n\n\t\t\t\treturn struct{}{}, fmt.Errorf(\"pulling image %s: %w\", imageName, pullErr)\n\t\t\t}\n\t\t\tdefer reader.Close()\n\n\t\t\tsink := io.Discard\n\t\t\tif verbose {\n\t\t\t\tsink = os.Stdout\n\t\t\t}\n\n\t\t\t_, copyErr := io.Copy(sink, reader)\n\t\t\tif copyErr != nil {\n\t\t\t\treturn struct{}{}, fmt.Errorf(\"reading pull output: %w\", copyErr)\n\t\t\t}\n\n\t\t\treturn struct{}{}, nil\n\t\t},\n\t\tbackoff.WithBackOff(backoff.NewExponentialBackOff()),\n\t\tbackoff.WithMaxElapsedTime(60*time.Second),\n\t)","sourceCodeStart":542,"sourceCodeEnd":578,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/cmd/hi/docker.go#L542-L578","documentation":"Returned inside `ensureImageAvailable`'s retry closure when `cli.ImagePull` itself errors and the error is not classified as permanent (`isPermanentDockerPullError`). It is retried with exponential backoff up to 60s total; if backoff is exhausted the error surfaces. Permanent errors (e.g. repository not found, auth failure) abort immediately via backoff.Permanent.","triggerScenarios":"Transient network failures to the registry; Docker Hub rate limiting (429 toomanyrequests); registry TLS issues; DNS hiccups — each retried. Non-existent tag (golang:<bad-version>) or denied repository becomes permanent and is returned unwrapped-classified.","commonSituations":"Unauthenticated Docker Hub pulls in CI hitting rate limits; corporate proxies intercepting registry traffic; pulling a GoVersion tag that was never published; flaky VPN/DNS.","solutions":["Manually pull to see the real error: `docker pull golang:<version>`.","If rate-limited: `docker login` or configure registry mirrors; consider retagging a locally cached image.","Verify the tag exists on Docker Hub for the configured GoVersion.","For persistent network issues, fix proxy/DNS, then re-run — retry budget is only 60s."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// verify the tag exists before configuring the run\nif err := exec.Command(\"docker\", \"manifest\", \"inspect\", \"golang:\"+cfg.GoVersion).Run(); err != nil {\n    return fmt.Errorf(\"golang:%s is not a published tag: %w\", cfg.GoVersion, err)\n}","typeGuard":null,"tryCatchPattern":"err := ensureImageAvailable(ctx, cli, image, verbose)\nif err != nil && strings.Contains(err.Error(), \"pulling image\") {\n    if strings.Contains(err.Error(), \"toomanyrequests\") {\n        // rate limited: docker login or mirror, then retry\n    } else if strings.Contains(err.Error(), \"not found\") {\n        // permanent: fix the tag; do not retry\n    }\n}","preventionTips":["Authenticate pulls in CI to avoid Hub rate limits.","Pre-pull images outside the test run.","Confirm tags exist before pinning GoVersion.","Fix proxy/DNS stability so the 60s backoff budget suffices."],"tags":["docker","image-pull","rate-limit","network","retry"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}