Learning outcomes
The class has two kinds of laptop
This is not an exotic topic. It is the reason one team’s image runs on three machines and fails on the fourth.
A container shares the host kernel, and it also runs the host’s instruction
set. An image contains compiled binaries, and a binary compiled for amd64 — an
Intel or AMD processor — is not executable on arm64 — an Apple Silicon Mac, a
Raspberry Pi, or an ARM server in a cloud.
| Machine | Architecture |
|---|---|
most school and office PCs, most CI runners |
|
Mac with M-series chip |
|
Raspberry Pi 4 and 5 |
|
newer cloud instances |
|
The practical consequence: the image somebody built on their MacBook may not start on the school server, and the one built by the pipeline may not start on their MacBook.
What the failure looks like
Worth memorising, because the message does not say "wrong architecture" in any obvious way.
exec /app/entrypoint.sh: exec format error
or, on a Mac running an amd64 image:
WARNING: The requested image's platform (linux/amd64) does not match
the detected host platform (linux/arm64/v8) and no specific platform was
requested
exec format error means: the kernel was asked to run a binary it does not
understand. It is the same error a Linux kernel gives for a Windows executable,
and for the same reason.
Inspect before you guess:
docker image inspect nginx:1.27 --format '{{.Os}}/{{.Architecture}}'
docker buildx imagetools inspect nginx:1.27 # all variants in the registry
uname -m # what this machine is
On macOS and Windows the picture is muddier, because Docker there emulates the foreign architecture with QEMU. The image runs — slowly, sometimes five to ten times slower, and occasionally with subtle failures in native libraries. "It works but it is inexplicably slow" is a symptom of this, not of your code.
One tag, several images
The mechanism that solves it is the image index — sometimes called a manifest list. A tag in a registry does not have to point at one image; it can point at a small document listing several, one per platform.
docker pull booking:0.3.0 fetches the index, the client looks up its own
platform and pulls that digest. Nobody has to know which machine they are on —
which is why every widely used base image is published this way, and why
nginx:1.27 simply works everywhere.
Note what this means for the digest: the index has a digest of its own, and each
variant has another. booking@sha256:aaa… is one specific architecture; the tag
is the portable thing.
Building one
docker buildx create --name multi --use --bootstrap # once per machine
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t ghcr.io/htl-leonding-example/booking:0.3.0 \
--push .
Two details that trip people up on the first attempt.
--push, not --load. A local image store holds one architecture, so a
multi-platform build cannot be loaded into it. It goes straight to a registry, or
you build one platform at a time with --load for local testing.
It is slower. Each platform is built separately, and the one that is not native is emulated. A two-platform build is more than twice the time of a single one — which is an argument for building them in the pipeline rather than on a laptop.
For a JVM application there is a shortcut worth knowing: the .jar is
architecture-independent, so only the base image differs. Choosing a base that is
already multi-architecture — eclipse-temurin:25-jre-alpine is — means the same
Dockerfile produces both variants with no further work. For anything with
native code — node-gyp, Python wheels with C extensions, Go with cgo — the
build genuinely happens twice.
Is it worth it?
| Build multi-arch | Do not bother |
|---|---|
the team has both Intel and Apple Silicon machines |
everybody, including the server, is |
it will run on a Raspberry Pi or an ARM cloud instance |
the image is only ever used by the pipeline |
other people pull the image |
it is a throwaway build for one experiment |
it is a base image others build on |
the build takes 20 minutes and nobody needs the second variant |
The honest default for a school project: if the team is mixed, build both; if not, build one and write down which. The second half of that sentence is the part that gets skipped, and it is what turns "it does not work on my machine" into a two-minute diagnosis instead of an afternoon.
--platform linux/amd64 on docker run forces a specific variant. On a Mac
it makes an amd64 image run under emulation, which is a useful way to reproduce
what the server will do — slowly, but faithfully.
|
Decisions
-
Every published project image states which platforms it supports, in the README next to the pull command.
-
Where the team has mixed architectures, images are built for
linux/amd64,linux/arm64. -
Multi-platform images are built and pushed by the pipeline, not from a laptop.
-
Base images are chosen multi-architecture where a choice exists.
-
An
exec format erroris diagnosed withdocker image inspectbefore anything else is tried.
Pitfalls
-
Assuming an image that runs locally runs everywhere. It runs on your architecture.
-
buildx build --platform a,b --load. It cannot work; the local store holds one architecture. -
Building multi-platform on a laptop for every experiment and losing ten minutes each time.
-
Reading
exec format erroras a broken entrypoint script and rewriting it. -
Pinning a digest instead of a tag and thereby pinning one architecture for everybody.
-
Silently relying on emulation, then being surprised that the same container is eight times slower on one machine.
Terminology
| Deutsch | English |
|---|---|
Prozessorarchitektur |
processor architecture |
Befehlssatz |
instruction set |
Abbildverzeichnis, Manifestliste |
image index, manifest list |
Plattform |
platform |
Emulation |
emulation |
native Erweiterung |
native extension |
Further reading
-
The Docker documentation on
buildxand multi-platform images -
Module
docker-images— the build this extends -
Module
gh-actions-pages— where the multi-platform build belongs