{"record":{"id":"9fa38fb94e1e0294","repo":"nextcloud/all-in-one","slug":"could-not-pull-image-imagename-attempt-attempt","errorCode":null,"errorMessage":"Could not pull image {imageName} (attempt {attempt}/{maxRetries}): {errorDetails}","messagePattern":"Could not pull image (.+?) \\(attempt (.+?)/(.+?)\\): (.+?)","errorType":"exception","errorClass":"\\Exception","httpStatus":500,"severity":"critical","filePath":"php/src/Docker/DockerActionManager.php","lineNumber":616,"sourceCode":"                        $interval = time() - $lastHeartbeat;\n                        if ($interval >= self::PULL_HEARTBEAT_INTERVAL_SECONDS) {\n                            $addToStreamingResponseBody(\".\", $container);\n                            $lastHeartbeat = $now;\n                        }\n                    }\n                }\n                if ($pullErrors !== []) {\n                    throw new \\Exception(implode('; ', $pullErrors));\n                }\n                break;\n            } catch (\\Exception $e) {\n                $errorDetails = $e instanceof RequestException\n                    ? $e->getResponse()?->getBody()->getContents()\n                    : $e->getMessage();\n                $message = \"Could not pull image \" . $imageName . \" (attempt $attempt/$maxRetries): \" . $errorDetails;\n                if ($attempt === $maxRetries) {\n                    if ($imageIsThere === false) {\n                        throw new \\Exception($message);\n                    } else {\n                        error_log($message);\n                    }\n                } else {\n                    error_log($message . ' Retrying...');\n                    sleep(1);\n                }\n            }\n        }\n    }\n\n    private function isContainerUpdateAvailable(string $id): string {\n        $container = $this->containerDefinitionFetcher->GetContainerById($id);\n\n        $updateAvailable = \"\";\n        if ($container->GetUpdateState() === VersionState::Different) {\n            $updateAvailable = '1';\n        }","sourceCodeStart":598,"sourceCodeEnd":634,"githubUrl":"https://github.com/nextcloud/all-in-one/blob/6b788eec5e61733cf03ed380a3572e43ae3f11ce/php/src/Docker/DockerActionManager.php#L598-L634","documentation":"The pull loop (maxRetries = 3, 1s sleep between attempts, streaming POST images/create with per-line error events) exhausted all attempts and the image was confirmed absent locally beforehand (GET images/{name}/json failed), so the last error is thrown with attempt count and details. If the image WAS already present locally, the failure is only logged via error_log and the method continues — the exception means there is no local fallback.","triggerScenarios":"Tag or repository does not exist ('manifest unknown'); Docker Hub anonymous pull rate limit (429 toomanyrequests); no route/DNS to the registry; private image without credentials; a transient network error repeating three times; every pull-stream event carried an 'error' key.","commonSituations":"Docker Hub rate limits on busy or shared-IP hosts; typo'd or not-yet-published AIO channel tag; air-gapped host without a registry mirror; restricted egress blocking registry-1.docker.io.","solutions":["Pull manually on the host: docker pull <imageName> to see the daemon's full error","On 429 toomanyrequests: wait the quota window or docker login (authenticated limits are higher), or configure a registry mirror","Verify the tag actually exists in the registry before deploying","Check DNS/egress from the docker host to the registry and retry once fixed"],"exampleFix":"// before: hard fail after 3 attempts\n// after: caller-level backoff with jitter before giving up\n$attempts = [5, 30, 120];\nforeach ($attempts as $i => $delay) {\n    try {\n        $docker->PullImage($container, $stream);\n        break;\n    } catch (\\Exception $e) {\n        if ($i === count($attempts) - 1) { throw $e; }\n        error_log('Pull failed, retrying in ' . $delay . 's: ' . $e->getMessage());\n        sleep($delay);\n    }\n}","handlingStrategy":"retry","validationCode":"// Pre-check local presence so an absent image + failing registry fails fast and clearly\ntry {\n    $docker->sendHttpRequest('GET', $docker->BuildApiUrl('images/' . rawurlencode($imageName) . '/json'));\n    $present = true;\n} catch (\\Throwable) {\n    $present = false; // pull is mandatory — ensure egress/quota before continuing\n}","typeGuard":null,"tryCatchPattern":"// Library already retries 3x internally; caller should retry with LONGER backoff\n// (rate-limit windows are minutes, so immediate retries cannot help)\ntry {\n    $docker->PullImage($container, $stream);\n} catch (\\Exception $e) {\n    if (str_contains($e->getMessage(), 'toomanyrequests')) {\n        scheduleRetry(15 * 60); // Docker Hub quota window\n        return;\n    }\n    throw $e;\n}","preventionTips":["docker login on the host raises Docker Hub pull limits — do it before running AIO updates","Configure a registry mirror or pull-through cache on rate-limited hosts","Remember the local-image fallback: if the image exists locally, pull failure is logged, not thrown — pre-pulling key images buys resilience"],"tags":["php","docker","images","registry","rate-limit","retry"],"backgroundTag":"docker-image-pull-failed","analyzedSha":"6b788eec5e61733cf03ed380a3572e43ae3f11ce","analyzedAt":"2026-08-21T05:47:24.382Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}