Built on an Apple Silicon Mac? Your arm64 Image Won't Deploy

·5 min read·Updated
dockerarm64apple-siliconbuildxregistrytroubleshooting

If you build a Docker image on an Apple Silicon Mac and push it, you get an arm64 image — and the platform runs linux/amd64. The frustrating part isn't the mismatch, it's how quietly it fails: the registry only tracks amd64/linux images, so your pushed tag may simply not appear in the tag list, and if you've enabled auto-deploy it is silently skipped rather than reported as an error. Nothing turns red. The fix is one flag — build with docker buildx build --platform linux/amd64 — and it's worth setting up once so you never debug this twice.

Why this happens at all

Docker builds for the architecture of the machine doing the building. On an Intel or AMD laptop that's amd64 and everything lines up by accident. On an M-series Mac it's arm64, and so is every image you build there unless you say otherwise.

That's invisible locally, because the image you built runs perfectly on the machine that built it. It only surfaces when the image lands somewhere else — which is the entire point of pushing it.

What actually happens when you push arm64

This is the part that makes it hard to diagnose, so here's the real behaviour rather than a guess:

  • The registry only records amd64/linux. A pushed manifest is inspected, and images that aren't amd64 + linux aren't tracked as usable tags. Your push reports success — the blobs upload fine — but the tag doesn't show up where you expect it.
  • A multi-arch push is fine. If you push a manifest list that includes an linux/amd64 entry, that entry is picked and tagged normally. Multi-platform builds work; arm64-only ones don't.
  • Auto-deploy skips it without telling you. If you've enabled auto-deploy on an app's Deployment tab, an image whose architecture isn't amd64/linux is skipped. That decision is logged on the server, not surfaced to you — so from your side, you push and nothing happens.

So the symptom set is: a push that "worked", a tag you can't find, and an app that never updates. No error message anywhere in the panel.

If instead you got the image onto a container some other way, the runtime symptom is the classic one:

exec /usr/local/bin/docker-entrypoint.sh: exec format error

That message means exactly one thing — the binary inside is built for a different CPU architecture than the machine trying to run it.

The fix

Build for the target platform explicitly:

docker buildx build --platform linux/amd64 -t <your-registry>/<repo>:v1 --push .

Two details worth knowing.

--push rather than -t then docker push. A buildx build for a non-native platform doesn't land in your local image store in the usual way, so pushing directly from the build is the path of least surprise.

Emulation is slow. Docker's own documentation is blunt about it: "Emulation with QEMU can be much slower than native builds, especially for compute-heavy tasks like compilation and compression or decompression" (Docker docs). For an interpreted app it's barely noticeable. For anything that compiles, expect a real wait.

If the wait bothers you, build both:

docker buildx build --platform linux/amd64,linux/arm64 -t <repo>:v1 --push .

You get an image that runs natively on your Mac and deploys here, because the platform picks the amd64 entry out of the manifest list.

Make it impossible to forget

One flag you have to remember is one flag you'll forget at the worst moment. Two ways to remove the choice:

Build in CI. GitHub Actions runners are amd64, so an image built there is correct by default with no flags at all. This also sidesteps emulation entirely — a native build on the right architecture. If you're already deploying from Actions, you've solved this without noticing.

Or let the platform build it. Push your source with git push instead of pushing an image, and the build happens on the platform's own amd64 infrastructure from your Dockerfile. Architecture stops being something you think about. The trade-off is the build container's memory ceiling, which a heavy build can hit.

Both of these are better than remembering a flag, and both are the reason this problem is mostly reported by people building locally on a laptop.

Where it bites, and where it doesn't

Deploy path Affected?
Docker push from an M-series Mac Yes — this is the case
Docker push from CI or an Intel machine No
Git push (platform builds it) No
Zip upload (platform builds it) No
Existing public image from Docker Hub No — official images are multi-arch and the amd64 variant is selected

Note the last row, because it's a common source of confusion: pulling postgres or redis from Docker Hub is fine. Those publish manifest lists, and "Docker automatically selects the correct variant based on the host's architecture." The problem is specific to images you build on arm64 hardware.

FAQ

Why doesn't my pushed image show up in the tag list?

Most likely it's arm64. Only amd64/linux images are tracked as usable tags, so an arm64-only push uploads successfully but doesn't appear.

Why did auto-deploy do nothing after I pushed?

An image whose architecture isn't amd64/linux is skipped. There's no error shown — the decision is logged server-side.

What does "exec format error" mean?

The binary in the image was built for a different CPU architecture than the machine running it. Almost always arm64 on amd64.

How do I build an amd64 image on an M1/M2/M3 Mac?

docker buildx build --platform linux/amd64 --push . Build and push in one step rather than pushing separately.

Can I build for both architectures?

Yes — --platform linux/amd64,linux/arm64. The resulting manifest list runs natively on your Mac and deploys here.

Is there a way to avoid thinking about this?

Build in CI, or push source and let the platform build. Both produce amd64 images with no flags.

Wrapping up

Architecture mismatch is a distinctly modern problem: it didn't exist when every laptop and every server were the same instruction set, and it appeared for a lot of developers the week they switched to Apple Silicon. What makes it costly here isn't the mismatch itself but the silence around it — a successful push, a missing tag, and an app that never updates. Add --platform linux/amd64 to your build, or move the build somewhere that's already amd64, and it stops being a category of bug you can encounter.