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.

Want structured learning?

Take the full Buildkit course →