BuildKit’s ability to cross-compile ARM64 Docker images is so seamless, you’d barely know it’s happening.
Let’s see it in action. Imagine you’re on an x86_64 machine and need to build an ARM64 image for a Raspberry Pi.
# Dockerfile
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y --no-install-recommends \
nginx \
&& rm -rf /var/lib/apt/lists/*
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
And a simple nginx.conf:
# nginx.conf
worker_processes 1;
events {
worker_connections 1024;
}
http {
server {
listen 80;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ =404;
}
}
}
Now, to build this for ARM64 from your x86_64 machine, you’d typically use docker buildx.
First, ensure you have buildx installed and a builder instance configured. If you don’t have one, create it:
docker buildx create --use
This command sets up a new builder instance, often using QEMU for emulation, which is key for cross-compilation.
Then, build your image specifying the target platform:
docker buildx build --platform linux/arm64 -t your-dockerhub-username/nginx-arm64:latest .
This command tells BuildKit to produce an image for the linux/arm64 architecture. BuildKit, through its buildx plugin, leverages QEMU user-mode emulation to run ARM64 binaries on your x86_64 host during the build process. It fetches the correct ARM64 base image, executes ARM64 commands within the build environment, and packages the resulting artifacts into an ARM64-compatible image. The your-dockerhub-username/nginx-arm64:latest part is the tag you’re giving your new image.
The problem this solves is the inability to natively build software for a different CPU architecture than your host machine. Historically, you’d need an actual ARM64 machine to compile ARM64 code. buildx with QEMU emulation removes this barrier, allowing developers to produce multi-architecture images from a single development environment.
Internally, when you specify --platform linux/arm64, buildx configures the build environment to use QEMU. For instance, if your Dockerfile has RUN apt-get update, QEMU intercepts the apt-get command, which is an x86_64 binary on your host, and emulates its execution as if it were running on an ARM64 system. This involves translating ARM64 instructions to x86_64 instructions on the fly. The base image pulled will also be the ARM64 variant (e.g., ubuntu:22.04 for linux/arm64).
The exact levers you control are primarily the --platform flag, which can accept comma-separated values for multi-arch builds (e.g., --platform linux/amd64,linux/arm64), and --push if you want to immediately push the resulting multi-arch image to a registry. You can also specify --output type=local,dest=./out to just get the built files without creating an image.
What most people don’t realize is that the --platform flag doesn’t just tell BuildKit what to build for; it actively configures the runtime environment for each build stage. If you’re building a multi-stage Dockerfile and the first stage is linux/amd64 and the second is linux/arm64, BuildKit will spin up separate QEMU environments for each stage with the appropriate emulation. This means you can even cross-compile between stages within a single docker buildx build command, which is incredibly powerful for complex applications where dependencies might need to be built for different architectures.
Once you’ve pushed your multi-arch image to a registry, Docker’s manifest list will automatically select the correct image for the host architecture when pulled.