A five-platform image that pulled without a single warning, docker image ls --tree printing linux/amd64 in the child rows, and the daemon refusing anyway:

Error response from daemon: no suitable export target found: image with reference
kserve/llmisvc-controller:v0.20.0 was found but does not provide the specified
platform (linux/amd64)

Short version: under Docker 29's containerd image store, that one string covers four different states of your local store, and the docs describe exactly one of them. docker buildx imagetools inspect tells you what the registry publishes, docker image ls --tree tells you what your disk actually holds, and a 0B in the CONTENT SIZE column is the difference between the two. The export that survives every cause is a pull by child digest.

The report above is docker/cli #7278, filed on 3 September 2026 against Docker 29.7.2 by an operator who wanted an amd64 tar for a Kind cluster.

What Docker 29 actually keeps when you pull one platform

Docker Engine 29.0.0 (10 November 2025) made the containerd image store the default for fresh installs and added --platform to docker image save and docker image load in the same release, per the 29 release notes. Those two changes interact badly.

Under the containerd store, a pull records the full OCI index and every child descriptor, then fetches blobs only for the platform you asked for. The manifest for linux/s390x is on your disk. Its layers never arrived. Every tool that walks the index sees seven platforms; every tool that walks content sees one. The index-of-manifests shape is the same one that breaks registries in other places, which is why Cosign v3 signatures start failing on Harbor and why scanner behaviour diverges per architecture when you are choosing between Trivy and Grype for a given job.

Registry index5 platformsLocal content store5 child manifestsall recordedBlobs for amd64 onlyother rows read 0Bdocker saveplatform lookupCause 1Cause 2Cause 3Cause 4tar is an OCI indexcontent digest not foundpull one platformplatform absent from indexmanifest yes blobs noshared layer bugflag omittedexport succeedsctr import all-platforms

The bookkeeping says the platform exists and the storage disagrees. It is the same class of failure as Kubernetes DRA handing one GPU to a VM and a pod at once: the record of the resource and the resource itself drifted apart, and only one of them is authoritative when something tries to use it.

Four causes behind one string

CauseError wordingSeparating commandFix
1. Platform never publisheddoes not provide the specified platformdocker buildx imagetools inspect <ref> omits itBuild the variant yourself; there is nothing to export
2. Manifest present, blobs absentdoes not provide the specified platformdocker image ls --tree shows 0B 0B on that rowPull the child manifest by digest
3. Shared-layer content-store bugdoes not provide any platformTwo --platform pulls sharing layers, then a pushPull by digest, or pull that image on its own
4. Host platform missingNotFound: content digest sha256:...: not foundFailing command has no --platform and the host arch row is 0BPass --platform explicitly

Cause 1 is the only one Docker documents. The save reference states that "An error is produced if the given platform is not present in the local image store," which is accurate and almost never what you are hitting.

Cause 2 is #7278, and before it docker/cli #6457 on 28.4.0, closed as not planned. The signal lives in docker image ls --tree, still flagged experimental in the CLI. Child rows carry DISK USAGE and CONTENT SIZE:

alpine:latest      beefdbd8a1da   10.6MB   3.37MB
├─ linux/riscv64   80cde017a105   10.6MB   3.37MB
├─ linux/amd64     33735bd63cf8   0B       0B
├─ linux/arm64/v8  9cee2b382fe2   0B       0B

Two zeros mean a descriptor with no content behind it. The daemon's phrasing, "was found but does not provide the specified platform," is a statement about the reference lookup. Operators read it as a statement about the manifest, and the wording collapses two different facts into one sentence.

Cause 3 is moby #52897, carrying status/confirmed against 29.5.3 with containerd v2.2.4. Pull rancher/mirrored-coredns-coredns:1.14.3 with --platform linux/amd64, pull rancher/mirrored-metrics-server:v0.8.1 the same way, then tag and push the second one:

image with reference localhost:5000/test:0617 was found but does not provide any platform

Pull the second image on its own and it works. Shared layers between the two are the trigger, and the classic image store is unaffected. Watch the noun: any platform instead of the specified platform. That single word is the cheapest discriminator in the family and it costs you nothing to read.

Cause 4 is moby #50173, a plain docker save with no --platform at all, on an amd64 host, after pulling only linux/arm64/v8:

unable to create manifests file: NotFound: content digest sha256:0800…: not found

The reporter's reading is that the export path silently substitutes the host platform rather than exporting everything present. It sits in the maintainers' Containerd-as-default project with kind/bug, next to moby #44578, which records the same host-platform default in docker image inspect and docker image rm.

There is a fifth, mostly closed: release 29.6.0 (18 June 2026) shipped a fix for "image selection with the containerd image store on amd64 hosts when images provide amd64 variant-specific manifests." If you are below 29.6.0 and the publisher ships linux/amd64/v3, upgrade before you triage anything else.

Which one am I hitting?

Two commands, in this order:

# 1. What does the registry actually publish?
docker buildx imagetools inspect kserve/llmisvc-controller:v0.20.0

# 2. What does the local store actually hold?
docker image ls --tree
docker image inspect --platform linux/amd64 kserve/llmisvc-controller:v0.20.0  # API 1.49+

Missing from step 1 is cause 1. Present in step 1 with 0B in step 2 is cause 2. Real bytes in step 2 with an error that says "any platform" is cause 3. No --platform on the failing command and a 0B host arch row is cause 4.

Why kind load dies on a digest you never pulled

kind load docker-image shells out to docker save and pipes the stream into ctr --namespace=k8s.io images import --all-platforms --digests --snapshotter=overlayfs - inside the node container. --all-platforms walks every child descriptor in the index, reaches the six with no blobs, and dies:

ctr: content digest sha256:…: not found

That is kind #4066 on kind 0.30 with Docker 29.1.2, and again on v0.32.0 in kind #4224. The digest belongs to an architecture the developer never asked for, which is why the error is close to ungoogleable for the person staring at it. Air-gapped delivery has the same shape (pull, save, ship the tar, load) with the same trap: the tar is now an OCI index, and the receiving runtime may or may not tolerate that. Local Docker dev loops accumulate these version-boundary failures, the same way pnpm deploy stopped finding modules inside Docker on 11.19.

Is switching the store off a real fix?

The workaround repeated across all these threads is features.containerd-snapshotter=false in /etc/docker/daemon.json. It works, and it has a price nobody quotes. You move the daemon back to the store Docker is retiring, you strand every image already in the containerd store behind a wall you cannot see while the disk stays consumed, and you give up multi-platform handling.

The engineer who disagrees runs a stable CI fleet on a pinned engine and wants zero variance until a maintenance window. That position is defensible for exactly as long as the pin holds, which in my experience is until someone rebuilds a runner image and picks up a fresh 29.x install with the containerd store back on by default. Buying six quiet months and paying for them with an unplanned migration is a trade worth making only if you have the migration scheduled.

What to change before your next kind load

  1. Check your engine version first. Below 29.6.0, upgrade before triaging: the amd64 variant-selection fix landed there and will resolve a subset of these outright.
  2. Read the noun in the error. "the specified platform" points at content presence, cause 2. "any platform" is moby #52897 and needs a digest pull.
  3. Run the two-command split (buildx imagetools inspect, then image ls --tree) before changing any daemon config. 0B in CONTENT SIZE is the only place the CLI states plainly that a platform row has no blobs behind it.
  4. Pin by child digest wherever you pinned by tag plus --platform. This is the form that survives all four causes and is more reproducible in CI regardless:
DIGEST=$(docker buildx imagetools inspect --raw kserve/llmisvc-controller:v0.20.0 \
  | jq -r '.manifests[] | select(.platform.os=="linux" and .platform.architecture=="amd64") | .digest')
docker pull kserve/llmisvc-controller@$DIGEST
docker save kserve/llmisvc-controller@$DIGEST -o out.tar
  1. For kind, skip the wrapper and drop the flag that causes the index walk:
docker save redis:7-alpine | docker exec -i kind-worker \
  ctr --namespace=k8s.io images import --digests -
  1. Audit every pull → save → load path you own, including air-gapped tar delivery. Grep your scripts for --all-platforms on the import side; that flag is the trigger.
  2. Review scripts that key off IMAGE ID. moby #51779 reports that image IDs under the containerd store now derive from the index digest rather than the platform-specific config digest, so anything comparing IDs across a store migration will lie to you in the same maintenance window.

FAQ

Why does docker image ls --tree show a platform that docker save says is missing? The tree view walks index descriptors; the export path walks content. A pull records all child manifests and fetches blobs for one platform, so the row exists with 0B in both size columns.

What is the difference between "does not provide the specified platform" and "does not provide any platform"? The first means the daemon looked for one platform and found no content for it. The second is the confirmed shared-layer bug in moby #52897 on 29.5.3 with containerd v2.2.4, where an image with real content reports no platforms at all.

Does disabling containerd-snapshotter delete my images? No. It hides them. The containerd store keeps its content on disk and consumes the space while the classic store is active, and the images reappear when you switch back.

Why does kind load docker-image fail with a digest I never pulled? kind imports with ctr images import --all-platforms, which iterates every child descriptor in the saved index, including the ones whose layers were never fetched.

How do I pin a single-platform image in CI so this stops happening? Resolve the child digest with docker buildx imagetools inspect --raw plus jq, then pull and save image@sha256:.... The store then holds one platform with nothing dangling.