docker/compose · error
reading blob %s: %w
Error message
reading blob %s: %w
What it means
The blob request succeeded but reading the response body (io.ReadAll over a LimitReader bounded by descriptor.Size+1) failed mid-transfer. This wraps the underlying I/O error with the digest: network reset, TLS renegotiation, proxy truncation, or the registry closing the stream early. The +1-byte bound means this is not a size problem (that is error 95) but a transport/read failure.
Source
Thrown at internal/oci/resolver.go:117
// 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 {
return nil, fmt.Errorf("invalid digest %s: %w", descriptor.Digest, err)
}
if actual := descriptor.Digest.Algorithm().FromBytes(content); actual != descriptor.Digest {
return nil, fmt.Errorf("blob digest mismatch: expected %s, got %s", descriptor.Digest, actual)
}
return content, nil
}
func Copy(ctx context.Context, resolver remotes.Resolver, image reference.Named, named reference.Named) (spec.Descriptor, error) {
src, desc, err := resolver.Resolve(ctx, image.String())
if err != nil {View on GitHub (pinned to ddc4b044b6)
Solutions
- Retry the pull — transient transport errors are the most common cause.
- If it fails repeatedly at the same blob, bypass intermediaries (VPN/proxy) or raise proxy read timeouts.
- Check registry/CDN health and logs for aborted uploads/GC races.
- Reduce artifact size (fewer/smaller layers) if timeouts keep truncating the stream.
Defensive patterns
Strategy: retry
Try / catch
content, err := io.ReadAll(io.LimitReader(fetch, descriptor.Size+1))
if err != nil {
if isTransientIOErr(err) { // reset, timeout, EOF mid-stream
return retryGetBlob(ctx, resolver, ref, descriptor) // bounded retries
}
return nil, fmt.Errorf("reading blob %s: %w", descriptor.Digest, err)
} Prevention
- Retry transient transport failures with backoff.
- Avoid proxies/VPNs that truncate long downloads.
- Keep artifacts small enough for your network's stability window.
When it happens
Trigger: fetcher.Fetch's Read fails partway: connection reset between registry and client, proxy idle-timeout killing long blob downloads, flaky VPN/Wi-Fi, registry closing the stream on internal error.
Common situations: Large compose artifact layers downloaded over unstable links; corporate proxies with aggressive timeouts; CDN edge nodes dropping slow streams; ephemeral network blips during docker compose pull.
Related errors
- creating fetcher for %s: %w
- fetching blob %s: %w
- blob %s size mismatch: expected %d bytes, got %d
- failed to pull OCI resource %q: %w
- unsupported OCI version: %s
AI-assisted analysis of docker/compose@ddc4b044b6 (2026-08-15).
Data as JSON: /api/errors/de17a8b7d52d6054.
Report an issue: GitHub.