GoogleContainerTools/skaffold · warning
could not remove image %q after %d retries
Error message
could not remove image %q after %d retries
What it means
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.
Source
Thrown at pkg/skaffold/docker/image.go:640
res, err := l.apiClient.ImageInspect(ctx, img, client.ImageInspectWithRawResponse(&raw))
if err != nil {
return image.InspectResponse{}, nil, err
}
return res.InspectResponse, raw.Bytes(), nil
}
func (l *localDaemon) ImageRemove(ctx context.Context, image string, opts client.ImageRemoveOptions) ([]image.DeleteResponse, error) {
for i := 0; i < retries; i++ {
res, err := l.apiClient.ImageRemove(ctx, image, opts)
if err == nil {
return res.Items, nil
}
if !errdefs.IsConflict(err) {
return nil, err
}
time.Sleep(sleepTime)
}
return nil, fmt.Errorf("could not remove image %q after %d retries", image, retries)
}
func (l *localDaemon) ImageList(ctx context.Context, ref string) ([]image.Summary, error) {
res, err := l.apiClient.ImageList(ctx, client.ImageListOptions{
Filters: client.Filters{}.Add("reference", ref),
})
if err != nil {
return nil, err
}
return res.Items, nil
}
func (l *localDaemon) DiskUsage(ctx context.Context) (uint64, error) {
usage, err := l.apiClient.DiskUsage(ctx, client.DiskUsageOptions{})
if err != nil {
return 0, err
}
return uint64(usage.Images.TotalSize), nilView on GitHub (pinned to a1189de023)
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.
Example fix
// before
client.ImageRemoveOptions{}
// after
client.ImageRemoveOptions{Force: true, PruneChildren: true} Defensive patterns
Strategy: fallback
Validate before calling
// find blockers before removing
cs, _ := client.ContainerList(ctx, client.ContainerListOptions{All: true})
for _, c := range cs { if c.ImageID == imageID { return fmt.Errorf("container %s uses image", c.ID) } } Try / catch
if _, err := daemon.ImageRemove(ctx, img, opts); err != nil && strings.Contains(err.Error(), "retries") {
// conflict: image in use — remove dependent containers/children first, then retry
removeContainersUsingImage(ctx, img)
_, err = daemon.ImageRemove(ctx, img, client.ImageRemoveOptions{Force: true, PruneChildren: true})
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- pruning images: %w
- %q running container image %q errored during run with status
- docker deployment not supported alongside cluster deployment
- can't merge defaultNamespace property from kustomize into ku
- INIT_DOCKER_NETWORK_INVALID_MODE
AI-assisted analysis of GoogleContainerTools/skaffold@a1189de023 (2026-09-05).
Data as JSON: /api/errors/eb374600e3e1de09.
Report an issue: GitHub.