Gotcha: docker inspect
can lie about the architecture of an image
This post's featured URL for sharing metadata is https://www.jvt.me/img/profile.jpg.
Today I've been working with some (internal) cross-compiled Docker images, while responding to an internal incident.
As part of this, we followed some documentation (I had written a few weeks ago) around how to build an image ad-hoc, and noticed that the images weren't correctly working - a colleague using an ARM Mac wasn't able to correctly build an image that worked on our x86 infrastructure.
What was weird is that we'd tested this when writing the docs and it seemed to build the image + binary correctly - but as we discovered today, docker inspect
was lying to us.
Following the official docs, and a blog post from 2021 which still holds up, we produced a Dockerfile
like:
FROM --platform=$BUILDPLATFORM golang:1.24 AS build
WORKDIR /app
COPY . .
ARG TARGETOS TARGETARCH
RUN GOOS=$TARGETOS GOARCH=$TARGETARCH go build .
FROM --platform=$BUILDPLATFORM alpine
# ^^^^^^^^^^^^^ is wrong
COPY --from=build /app/hello-world /bin
When building this image locally (without cross-compiling) everything worked:
% docker build .
...
=> => writing image sha256:8ac63a06a83515e253795be92a6966dc77e9e259473abb747769902e85049e3c
% docker run -ti sha256:8ac63a06a83515e253795be92a6966dc77e9e259473abb747769902e85049e3c /bin/hello-world
Hello there
And it looks like it produced an x86 image:
% docker inspect sha256:8ac63a06a83515e253795be92a6966dc77e9e259473abb747769902e85049e3c
[
{
"Id": "sha256:8ac63a06a83515e253795be92a6966dc77e9e259473abb747769902e85049e3c",
"Architecture": "amd64",
"Os": "linux"
}
]
And we can build it for a different architecture:
% docker build --platform linux/arm64 .
...
=> => writing image
Naturally, as this is cross-compiled, it shouldn't allow us to start the container:
% docker run sha256:b837b97f705342e28bb90cc5cc592a37916c4ac640e41f97e42287bdc5dfdaa7 /app/hello-world
WARNING: The requested image's platform (linux/arm64) does not match the detected host platform (linux/amd64/v4) and no specific platform was requested
docker: Error response from daemon: failed to create task for container: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: error during container init: exec: "/app/hello-world": stat /app/hello-world: no such file or directory
And we can see that - correctly - this is an ARM64 image:
% docker inspect sha256:07db3c064d85270a7e62586339ef9f063a3af02e5b636921fcaf26c6afc08476
[
{
"Architecture": "arm64",
"Variant": "v8",
"Os": "linux"
}
]
However what went wrong was when my colleague built it - on an ARM Mac - and cross-compiled the Go app and the image for x86.
When I ran the Go app in the image itself, it all worked:
$ docker run -ti $image
{"log_level": "INFO", ...}
And it correctly reported itself as an x86 image:
% docker inspect $image
[
{
"Id": "...",
"Architecture": "amd64",
"Os": "linux"
}
]
The problem presented itself when I tried to execute any other binaries - i.e. not the Go binary we'd cross-compiled - at which point we noticed:
$ docker run -ti $image sh
exec /bin/sh: format error
As I've written about before, this is down to the sh
binary having the wrong CPU architecture.
But wait - docker inspect
says it's an amd64
AKA x86 image - what??
The root cause was that the Dockerfile
was wired in incorrectly, and that instead we should have written:
FROM --platform=$BUILDPLATFORM golang:1.24 AS build
WORKDIR /app
COPY . .
ARG TARGETOS TARGETARCH
RUN GOOS=$TARGETOS GOARCH=$TARGETARCH go build .
-FROM --platform=$BUILDPLATFORM alpine
+FROM --platform=$TARGETPLATFORM alpine
COPY --from=build /app/hello-world /bin
Or, it appears, simply:
FROM --platform=$BUILDPLATFORM golang:1.24 AS build
WORKDIR /app
COPY . .
ARG TARGETOS TARGETARCH
RUN GOOS=$TARGETOS GOARCH=$TARGETARCH go build .
-FROM --platform=$BUILDPLATFORM alpine
+FROM alpine
COPY --from=build /app/hello-world /bin
Because of this, we told Docker that the image is an x86, even though it wasn't.
What's weird here is that Docker kinda knew that it was building the final image as an ARM image, because all the binaries in it were ARM, but because --platform=$BUILDPLATFORM
seems to take priority, it didn't work.