docker/compose · error

creating fetcher for %s: %w

Error message

creating fetcher for %s: %w

What it means

GetBlob asks the containerd remotes.Resolver for a Fetcher bound to the repository reference; if that fails (auth, repository does not exist, resolver misconfiguration, unreachable registry) the error is wrapped with the ref. This happens before any blob request is made, so it always indicates a reference/registry-level problem, not a problem with the specific blob.

Source

Thrown at internal/oci/resolver.go:106

	fetch, err := fetcher.Fetch(ctx, descriptor)
	if err != nil {
		return spec.Descriptor{}, nil, err
	}
	content, err := io.ReadAll(fetch)
	if err != nil {
		return spec.Descriptor{}, nil, err
	}
	return descriptor, content, nil
}

// GetBlob retrieves the content of a blob descriptor (e.g. an artifact layer)
// from the repository ref belongs to. Unlike Get it doesn't Resolve the
// digest, as the registry manifests endpoint only serves actual manifests;
// blob content must be fetched directly from the blobs endpoint.
func GetBlob(ctx context.Context, resolver remotes.Resolver, ref reference.Named, descriptor spec.Descriptor) ([]byte, error) {
	fetcher, err := resolver.Fetcher(ctx, ref.String())
	if err != nil {
		return nil, fmt.Errorf("creating fetcher for %s: %w", ref, err)
	}
	fetch, err := fetcher.Fetch(ctx, descriptor)
	if err != nil {
		return nil, fmt.Errorf("fetching blob %s: %w", descriptor.Digest, err)
	}
	defer func() { _ = fetch.Close() }()
	// bound the read by the declared size so a rogue registry can't cause
	// unbounded allocation; the extra byte detects oversized responses.
	content, err := io.ReadAll(io.LimitReader(fetch, descriptor.Size+1))
	if err != nil {
		return nil, fmt.Errorf("reading blob %s: %w", descriptor.Digest, err)
	}
	if int64(len(content)) != descriptor.Size {
		return nil, fmt.Errorf("blob %s size mismatch: expected %d bytes, got %d", descriptor.Digest, descriptor.Size, len(content))
	}
	// GetBlob bypasses containerd's content store, so integrity must be
	// checked here before callers write the bytes to disk.
	if err := descriptor.Digest.Validate(); err != nil {

View on GitHub (pinned to ddc4b044b6)

Solutions

  1. Authenticate against the registry: docker login <registry> (or configure the resolver's auth).
  2. Verify the ref: the repository must exist and the name be fully qualified (registry/account/repo).
  3. Check connectivity/TLS: curl -v https://<registry>/v2/ and inspect proxy/CA settings.
  4. Re-run with debug logging to see the wrapped containerd error, which names the real cause (401, 404, TLS, DNS).

Example fix

# before
$ docker compose --project-name demo pull   # not logged in to private registry

# after
$ docker login registry.example.com
$ docker compose --project-name demo pull
Defensive patterns

Strategy: try-catch

Validate before calling

// preflight: does the repo resolve and are credentials in place?
if _, err := resolver.Resolve(ctx, ref.String()); err != nil {
    return fmt.Errorf("cannot access %s (auth/repo): %w", ref, err)
}

Try / catch

fetcher, err := resolver.Fetcher(ctx, ref.String())
if err != nil {
    if errors.Is(err, errdefs.ErrUnauthorized) || errors.Is(err, errdefs.ErrNotFound) {
        // guide the user: login or check repo name
    }
    return fmt.Errorf("creating fetcher for %s: %w", ref, err)
}

Prevention

When it happens

Trigger: resolver.Fetcher(ctx, ref.String()) failing: unauthenticated pull from a private repo, expired registry token/credentials, typo'd repository name in the ref, network/DNS failure reaching the registry, or a resolver built without the right auth handler.

Common situations: docker compose pull/push of an OCI artifact from a private registry without docker login; stale cached credentials; corporate proxies intercepting registry TLS; refs normalized incorrectly (missing namespace/tag).

Related errors


AI-assisted analysis of docker/compose@ddc4b044b6 (2026-08-15). Data as JSON: /api/errors/7aa8c3d06f87701e. Report an issue: GitHub.