{"record":{"id":"eb374600e3e1de09","repo":"GoogleContainerTools/skaffold","slug":"could-not-remove-image-q-after-d-retries","errorCode":null,"errorMessage":"could not remove image %q after %d retries","messagePattern":"could not remove image %q after (.+?) retries","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"pkg/skaffold/docker/image.go","lineNumber":640,"sourceCode":"\tres, err := l.apiClient.ImageInspect(ctx, img, client.ImageInspectWithRawResponse(&raw))\n\tif err != nil {\n\t\treturn image.InspectResponse{}, nil, err\n\t}\n\treturn res.InspectResponse, raw.Bytes(), nil\n}\n\nfunc (l *localDaemon) ImageRemove(ctx context.Context, image string, opts client.ImageRemoveOptions) ([]image.DeleteResponse, error) {\n\tfor i := 0; i < retries; i++ {\n\t\tres, err := l.apiClient.ImageRemove(ctx, image, opts)\n\t\tif err == nil {\n\t\t\treturn res.Items, nil\n\t\t}\n\t\tif !errdefs.IsConflict(err) {\n\t\t\treturn nil, err\n\t\t}\n\t\ttime.Sleep(sleepTime)\n\t}\n\treturn nil, fmt.Errorf(\"could not remove image %q after %d retries\", image, retries)\n}\n\nfunc (l *localDaemon) ImageList(ctx context.Context, ref string) ([]image.Summary, error) {\n\tres, err := l.apiClient.ImageList(ctx, client.ImageListOptions{\n\t\tFilters: client.Filters{}.Add(\"reference\", ref),\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn res.Items, nil\n}\n\nfunc (l *localDaemon) DiskUsage(ctx context.Context) (uint64, error) {\n\tusage, err := l.apiClient.DiskUsage(ctx, client.DiskUsageOptions{})\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\treturn uint64(usage.Images.TotalSize), nil","sourceCodeStart":622,"sourceCodeEnd":658,"githubUrl":"https://github.com/GoogleContainerTools/skaffold/blob/a1189de023efc32d4b8e11f395acc678aa555011/pkg/skaffold/docker/image.go#L622-L658","documentation":"ImageRemove retries removing an image up to `retries` times, sleeping between attempts, but only while the daemon returns a conflict error (errdefs.IsConflict — the image is in use). If every retry hits the conflict, it gives up with \"could not remove image %q after %d retries\". Any non-conflict error is returned immediately and does NOT produce this message.","triggerScenarios":"Repeated ImageRemove(ctx, image, opts) calls where the Docker daemon keeps returning HTTP 409 Conflict for the full retry window — typically a container still exists that is based on the image, or a child image (dependent layer) is referenced.","commonSituations":"Trying to prune an image whose container (even stopped) still exists, image untagging blocked by dependent images, concurrent skaffold processes holding the image, k8s/kind cluster node still using the image.","solutions":["Remove containers using the image first: `docker ps -a --filter ancestor=<image>` then `docker rm <container>`.","Force removal with the force option (client.ImageRemoveOptions{Force: true}) if the caller's opts allow it.","Remove dependent child images before the parent (`docker rmi <child>` first).","Retry later if another process holds the image; check for concurrent skaffold/docker activity."],"exampleFix":"// before\nclient.ImageRemoveOptions{}\n// after\nclient.ImageRemoveOptions{Force: true, PruneChildren: true}","handlingStrategy":"fallback","validationCode":"// find blockers before removing\ncs, _ := client.ContainerList(ctx, client.ContainerListOptions{All: true})\nfor _, c := range cs { if c.ImageID == imageID { return fmt.Errorf(\"container %s uses image\", c.ID) } }","typeGuard":null,"tryCatchPattern":"if _, err := daemon.ImageRemove(ctx, img, opts); err != nil && strings.Contains(err.Error(), \"retries\") {\n    // conflict: image in use — remove dependent containers/children first, then retry\n    removeContainersUsingImage(ctx, img)\n    _, err = daemon.ImageRemove(ctx, img, client.ImageRemoveOptions{Force: true, PruneChildren: true})\n}","preventionTips":["Clean up containers derived from an image before removing the image.","Use Force/PruneChildren options when children block removal.","Avoid concurrent processes pruning the same images.","Schedule prunes when workloads are not actively pulling images."],"tags":["docker","image-remove","conflict","retry"],"backgroundTag":"docker-image-conflict","analyzedSha":"a1189de023efc32d4b8e11f395acc678aa555011","analyzedAt":"2026-09-05T12:09:27.064Z","contentChangedAt":"2026-09-05T12:09:27.064Z","schemaVersion":2},"datasetVersion":"2026-09-12T17:17:11.597Z"}