Skip to content
Latchkey LogoLatchkey home

Docker image does not match the specified platform in CI

Docker image does not match the specified platform means the daemon found the reference you named and refused to hand it over, because the image it holds is built for a different operating system or architecture than the one your command asked for. Nothing is corrupt and nothing failed to download: the daemon is declining to pretend that an amd64 image is an arm64 image.

Diagram of a manifest list, a single-arch cache entry and the platform the daemon was asked for
The daemon matches one stored image against one requested platform. A manifest list is what lets that match succeed twice.

What this error means

A pull, a run or a compose up stops on a message that names your image and two platforms, and the job had been working until somebody added a --platform flag, switched a runner to arm64, or introduced a matrix with two architectures in it. The exact wording depends on which Docker the runner has, which matters more here than on most errors: the message was rewritten in the Docker 27 series, so the line in your log may not match the line in the answer you found.

Reconstructed from the format string in moby daemon/images/image.go at v28.0.4, not a recorded run
image with reference myorg/api:1.4.2 was found but its platform (linux/amd64) does not match the specified platform (linux/arm64)

Which wording you get tells you which Docker you are on

This message has two forms and they are the same failure. The daemon emits it from daemon/images/image.go in moby, and the format string there changed during the 27 series: releases up to v27.3.1 print the older wording that names a wanted and an actual platform, and v27.4.0 onward print the newer wording that puts both platforms in parentheses. Nothing about the behavior changed with it.

This matters in CI because the runner image is usually older than the newest Docker. When we read the GitHub-hosted image manifests on 2026-09-21, ubuntu-22.04 and ubuntu-24.04 both listed Docker Client and Server 28.0.4, so a job on a hosted Linux runner prints the newer wording, while most of the answers written about this error quote the older one.

Docker Engine releaseThe wording the daemon prints
v20.10 through v27.3.1was found but does not match the specified platform: wanted <p>, actual: <p>
v27.4.0 through v28.5.2was found but its platform (<p>) does not match the specified platform (<p>)
On GitHub-hosted ubuntu-22.04 and ubuntu-24.04Docker 28.0.4, so the second form

Common causes

The local store holds the image for one platform only

The dominant cause. An earlier pull, build or load put a single-architecture image under that reference, and a later command asked for a platform it cannot serve. The reference resolves, which is why the error says the image was found, and the platforms disagree, which is why it stops there.

The image was never published for the platform you want

Some images exist for amd64 and nothing else, and no local action will change that. Inspecting the registry rather than the local store is what separates this from the cache case, and the two have completely different fixes: one is a re-pull, the other is a build.

A matrix introduced a second architecture and nothing else changed

In our experience this is the version that reaches CI. A workflow gains an arm64 leg, every reference in it stays the same, and the legs that were fine keep being fine while the new one cannot resolve images that only ever existed for x64.

The requested platform came from somewhere you did not look

A platform: key in a compose file, a DOCKER_DEFAULT_PLATFORM variable exported by an earlier step, or a --platform on a builder can all set the request without appearing on the command that failed. When the platforms in the message are not the ones you expected, the request is what to check first.

How to fix it

Publish a manifest list covering every platform you consume

  1. Build with buildx for all target platforms in the job that publishes the image.
  2. Confirm the list with docker buildx imagetools inspect before anything depends on it.
  3. Drop the per-caller --platform flags once the list exists, so callers stop needing to know.
Terminal
docker buildx build --platform linux/amd64,linux/arm64 -t myorg/api:1.4.2 --push .
docker buildx imagetools inspect myorg/api:1.4.2

Clear the stale entry, then pull for the platform you need

When the registry does have the platform and the local store is the problem, remove the cached image and pull again with the platform stated. Removing first matters: a pull that finds a matching reference may not replace an entry recorded under a different architecture.

Terminal
docker image rm myorg/api:1.4.2 || true
docker pull --platform linux/arm64 myorg/api:1.4.2

Set the platform once for the job rather than per command

If a whole job targets one architecture, say so once in the job environment instead of threading a flag through every command. One place to read is one place to change when the matrix grows, and it stops half the commands in a job disagreeing with the other half.

.github/workflows/ci.yml
jobs:
  arm:
    runs-on: ubuntu-24.04-arm
    env:
      DOCKER_DEFAULT_PLATFORM: linux/arm64

Print the platform in the job before anything depends on it

One inspect line in the log turns this error from a surprise into a fact you already had. Record what the store holds and what the runner is, right after the pull, so the next failure is diagnosed from the log instead of from a rerun.

Terminal
docker image inspect "$IMAGE" --format '{{.Os}}/{{.Architecture}}'
uname -m

A cached single-arch image is the usual reason

The daemon stores an image against the platform it fetched, and a plain pull fetches the platform of the host. When a later command asks for a different platform, the store has an entry under that reference that cannot satisfy it, and rather than silently running the wrong architecture the daemon refuses. The refusal is the feature; the confusion is that the reference clearly exists.

In a pipeline this shows up when one job pulls for the runner architecture and a later job, or a later matrix leg, asks for another. It also shows up on developer machines with an arm64 laptop and an amd64 registry image, which is why so many reports of this error come from people who were not doing anything with CI at all.

Terminal
# what is actually in the store, and for which platform
docker image inspect myorg/api:1.4.2 --format '{{.Os}}/{{.Architecture}}'
# what the registry can offer
docker buildx imagetools inspect myorg/api:1.4.2

The durable fix is a manifest list, not a flag

Pulling with an explicit platform solves the job in front of you and leaves the next one exposed, because it fixes the request rather than the image. If an image is used from more than one architecture, publish it for more than one architecture, and then no caller needs a flag at all.

Build the manifest list once in the job that publishes, and check it once in the job that consumes. An image that advertises two platforms satisfies both requests from the same reference, and the error stops being reachable rather than being handled.

.github/workflows/ci.yml
- name: Build and push a manifest list
  uses: docker/build-push-action@v6
  with:
    platforms: linux/amd64,linux/arm64
    push: true
    tags: myorg/api:1.4.2

What the runner does about it

No repair, and no recorded run. The reason is the shape of the reproduction rather than any shyness about the result: an honest capture of this failure needs two architectures present in one place, because the whole error is a disagreement between a stored platform and a requested one. Our reproduction harness runs x64 Linux runners, so anything we recorded would show us passing --platform linux/arm64 to a daemon that had never held an arm64 image, which demonstrates the message without demonstrating the condition that produces it in real pipelines.

The source settles what the message says and the runner image manifests settle which wording you will see, which is what this page needed. If you want the failure in front of you on a runner, add an arm64 leg to a matrix that pulls a single-arch image, and you will have it in one run.

How to prevent it

  • Publish multi-arch manifest lists for any image more than one architecture consumes.
  • Keep the requested platform in one place per job, not on individual commands.
  • Inspect the registry, not the local store, before concluding a platform does not exist.
  • Log the image platform and the runner architecture once per job that pulls images.

Frequently asked questions

Why does the message look different from every answer I can find?
Because the daemon reworded it during the Docker 27 series. Up to v27.3.1 it named a wanted and an actual platform; from v27.4.0 it puts both in parentheses instead. Hosted Linux runners were on Docker 28.0.4 when we checked on 2026-09-21, so your CI log shows the newer form while most published answers quote the older one.
Is the image broken or missing?
Neither. The daemon says it found the reference, which means the name resolved and the data is there. What it will not do is serve an image built for one platform to a request for another, so this is a refusal rather than a failure to fetch.
Does --platform on docker run fix this permanently?
It fixes the command you put it on. If the image genuinely exists only for one architecture, the flag will fail again the moment the requested platform is the missing one, and if the image exists for several, publishing a manifest list removes the need for the flag entirely. Treat the flag as a diagnosis and the manifest list as the fix.
Which component emits this message, the CLI or the daemon?
The daemon. It is built in moby daemon/images/image.go and travels back to the client inside an API error, which is why it reaches your terminal behind the Error response from daemon: prefix the CLI adds. The CLI does not decide anything about platform matching.

Related guides

References

Two architectures is two builds. Latchkey runs both at $0.0025/min at 2 vCPU with a persistent layer cache. Start free → 30-day trial · No credit card