containerd/containerd · error · errdefs.ErrNotFound

no push hosts: %w

Error message

no push hosts: %w

What it means

containerd's docker pusher filters its configured registry hosts down to those with the push capability. If no host declares push capability, push() aborts immediately with errdefs.ErrNotFound wrapped in this message before any network request is made. It means the remotes configuration simply has no usable push endpoint, not a network or auth problem.

Source

Thrown at core/remotes/docker/pusher.go:109

	status, err := p.tracker.GetStatus(ref)
	if err == nil {
		if status.Committed && status.Offset == status.Total {
			return nil, fmt.Errorf("ref %v: %w", ref, errdefs.ErrAlreadyExists)
		}
		if unavailableOnFail && status.ErrClosed == nil {
			// Another push of this ref is happening elsewhere. The rest of function
			// will continue only when `errdefs.IsNotFound(err) == true` (i.e. there
			// is no actively-tracked ref already).
			return nil, fmt.Errorf("push is on-going: %w", errdefs.ErrUnavailable)
		}
		// TODO: Handle incomplete status
	} else if !errdefs.IsNotFound(err) {
		return nil, fmt.Errorf("failed to get status: %w", err)
	}

	hosts := p.filterHosts(HostCapabilityPush)
	if len(hosts) == 0 {
		return nil, fmt.Errorf("no push hosts: %w", errdefs.ErrNotFound)
	}

	var (
		isManifest bool
		existCheck []string
		host       = hosts[0]
	)

	if images.IsManifestType(desc.MediaType) || images.IsIndexType(desc.MediaType) {
		isManifest = true
		existCheck = getManifestPath(p.object, desc.Digest)
	} else {
		existCheck = []string{"blobs", desc.Digest.String()}
	}

	req := p.request(host, http.MethodHead, existCheck...)
	if err := req.addNamespace(p.refspec.Hostname()); err != nil {
		return nil, err

View on GitHub (pinned to 4246446a2b)

Solutions

  1. Add or fix the host configuration so at least one host includes docker.HostCapabilityPush in its Capabilities.
  2. Verify the registry host is actually configured for the reference's hostname (check containerd config.toml [plugins."io.containerd.grpc.v1.cri".registry] or client.WithRegistryHost).
  3. If you only intend to pull, switch the operation to a pull path; push cannot proceed without a push-capable host.
  4. Confirm you are not overriding hosts with client.WithScheme/WithHosts incorrectly (scheme/host typo can yield an empty match).

Example fix

// before
hosts := []docker.Host{{Host: "mirror.example.com", Capabilities: docker.HostCapabilityPull}}
// after
hosts := []docker.Host{{Host: "registry.example.com", Capabilities: docker.HostCapabilityPull | docker.HostCapabilityPush}}
Defensive patterns

Strategy: validation

Validate before calling

// before pushing, verify a push-capable host exists for the ref
hosts := pusherConfig.Hosts(refspec.Hostname())
if len(hosts) == 0 {
    return fmt.Errorf("no host configured for %s", refspec.Hostname())
}
for _, h := range hosts {
    if h.Capabilities&docker.HostCapabilityPush != 0 {
        return nil // ok
    }
}
return fmt.Errorf("no push-capable host for %s", refspec.Hostname())

Try / catch

if err := pusher.Push(ctx, desc); err != nil {
    if strings.Contains(err.Error(), "no push hosts") {
        // fix host config, do not retry
    }
    return err
}

Prevention

When it happens

Trigger: Calling Writer (content store writer) or Push with a resolver/host config where every configured host lacks HostCapabilityPush — e.g. hosts built as mirror/pull-only, or an empty hosts list for the registry.

Common situations: Configuring containerd with registry.mirrors (pull-only) and then trying to push; using a client.Config host entry without docker.HostCapabilityPush; pushing through a proxy endpoint that was set up solely for pulling; mis-resolved default host (e.g. docker.io not configured).

Related errors


AI-assisted analysis of containerd/containerd@4246446a2b (2026-09-02). Data as JSON: /api/errors/d9618731691d8377. Report an issue: GitHub.