
In this post I describe how to create multi-architecture docker images. Specifically, I show how to create Docker images that run on ARM 64 processors (such as AWS's Graviton2 processors) from a Windows PC using Docker Desktop.
Docker multi-arch images
Docker has the concept of multi-architecture images, which means that a single Docker image can support multiple architectures. Typically different OS/processor architectures require different Docker images. With multi-arch images you specify a single image, and Docker will pull the appropriate architecture for your processor and platform.
For example, if you specify the .NET SDK docker image in your Dockerfile:
FROM mcr.microsoft.com/dotnet/sdk:5.0
#...
Then on Windows, if you're using Windows containers, it will pull the appropriate Windows Nano Server container. This could be version 1809, 2004, or 20H2, depending on your host OS version.
On a Linux system, this would pull the x64, arm64, or arm32 image, depending on your host architecture!
While working on the CI for the Datadog Tracer, I wanted to use dadarek/docker-wait-for-dependencies to ensure docker-compose has started all the dependency containers before we run our integration tests.
The problem was that the dadarek/docker-wait-for-dependencies
docker image doesn't support ARM64, so we were getting failures in CI when trying to run the image on Linux
In general, you can't run docker images that target a different processor architecture than your hose system. However, you can run Linux architectures like ARM64 on Windows using Docker Desktop. Docker Desktop uses the qemu-static emulator to make this cross-architecture emulation completely seamless!
As the repository and Dockerfile for dadarek/docker-wait-for-dependencies
are open source, I decided to rebuild the dockerfile as a multi-arch file that supports ARM64. Luckily, this is very easy to do with Docker's new buildx
command,
Creating multi-arch docker images with buildx
In this section I describe the steps I took, based on the documentation about the buildx command.
We start by cloning the docker-wait-for-dependencies repository:
git clone git@github.com:dadarek/docker-wait-for-dependencies.git
cd docker-wait-for-dependencies
You can see what platforms are supported by buildx
by running docker buildx ls
:
> docker buildx ls
NAME/NODE DRIVER/ENDPOINT STATUS PLATFORMS
default * docker
default default running linux/amd64, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/arm/v7, linux/arm/v6
This lists the supported platforms you can build from. In my case, this includes the following platforms:
linux/amd64
: Linux x64linux/arm64
: ARM 64linux/riscv64
: 64-bit RISC-Vlinux/ppc64le
: 64-bit little-endian PowerPClinux/s390x
: 64-bit Linux on IBM Zlinux/386
: Linux x86linux/arm/v7
: ARM v7 (32-bit)linux/arm/v6
: ARM v6 (32-bit)
I'm running Docker Desktop on Windows, using Linux containers, with Docker version 20.10.5.
We start by creating a new builder that supports the multi-arch platforms, calling it mybuilder
. Specifying --use
sets this as the current builder:
docker buildx create --name mybuilder --use
Building a multi-arch image is as simple as using docker buildx build
and passing the desired platforms using --platform
as a comma separated list:
docker buildx build --platform <Platforms> --push .
Using the following command, I built the docker image for multiple architectures, tagged it as andrewlock/wait-for-dependencies:latest
, and pushed to docker hub:
$ docker buildx build -t andrewlock/wait-for-dependencies:latest --platform linux/amd64,linux/arm64,linux/ppc64le,linux/s390x,linux/386,linux/arm/v7,linux/arm/v6 .
[+] Building 36.0s (28/28) FINISHED
=> [internal] booting buildkit 26.4s
=> => pulling image moby/buildkit:buildx-stable-1 25.0s
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 31B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [linux/arm/v6 internal] load metadata for docker.io/library/alpine:3.6 11.2s
=> [linux/ppc64le internal] load metadata for docker.io/library/alpine:3.6 11.1s
=> [auth] library/alpine:pull token for registry-1.docker.io 0.0s
=> [linux/arm64 internal] load metadata for docker.io/library/alpine:3.6 11.0s
=> [linux/386 internal] load metadata for docker.io/library/alpine:3.6 11.0s
=> [linux/arm/v7 internal] load metadata for docker.io/library/alpine:3.6 11.0s
=> [linux/amd64 internal] load metadata for docker.io/library/alpine:3.6 11.0s
=> [linux/s390x internal] load metadata for docker.io/library/alpine:3.6 10.9s
=> [internal] load build context 0.0s
=> => transferring context: 35B 0.0s
=> [linux/amd64 1/2] FROM docker.io/library/alpine:3.6@sha256:66790a2b79e1ea3e1dabac43990c54aca5d1ddf268d9a5a0285e4167c8b24475 0.2s
=> => resolve docker.io/library/alpine:3.6@sha256:66790a2b79e1ea3e1dabac43990c54aca5d1ddf268d9a5a0285e4167c8b24475 0.2s
=> [linux/arm/v7 1/2] FROM docker.io/library/alpine:3.6@sha256:66790a2b79e1ea3e1dabac43990c54aca5d1ddf268d9a5a0285e4167c8b24475 0.2s
=> => resolve docker.io/library/alpine:3.6@sha256:66790a2b79e1ea3e1dabac43990c54aca5d1ddf268d9a5a0285e4167c8b24475 0.2s
=> [linux/386 1/2] FROM docker.io/library/alpine:3.6@sha256:66790a2b79e1ea3e1dabac43990c54aca5d1ddf268d9a5a0285e4167c8b24475 0.2s
=> => resolve docker.io/library/alpine:3.6@sha256:66790a2b79e1ea3e1dabac43990c54aca5d1ddf268d9a5a0285e4167c8b24475 0.2s
=> [linux/arm/v6 1/2] FROM docker.io/library/alpine:3.6@sha256:66790a2b79e1ea3e1dabac43990c54aca5d1ddf268d9a5a0285e4167c8b24475 0.0s
=> => resolve docker.io/library/alpine:3.6@sha256:66790a2b79e1ea3e1dabac43990c54aca5d1ddf268d9a5a0285e4167c8b24475 0.1s
=> [linux/arm64 1/2] FROM docker.io/library/alpine:3.6@sha256:66790a2b79e1ea3e1dabac43990c54aca5d1ddf268d9a5a0285e4167c8b24475 0.2s
=> => resolve docker.io/library/alpine:3.6@sha256:66790a2b79e1ea3e1dabac43990c54aca5d1ddf268d9a5a0285e4167c8b24475 0.1s
=> [linux/s390x 1/2] FROM docker.io/library/alpine:3.6@sha256:66790a2b79e1ea3e1dabac43990c54aca5d1ddf268d9a5a0285e4167c8b24475 0.2s
=> => resolve docker.io/library/alpine:3.6@sha256:66790a2b79e1ea3e1dabac43990c54aca5d1ddf268d9a5a0285e4167c8b24475 0.1s
=> [linux/ppc64le 1/2] FROM docker.io/library/alpine:3.6@sha256:66790a2b79e1ea3e1dabac43990c54aca5d1ddf268d9a5a0285e4167c8b24475 0.2s
=> => resolve docker.io/library/alpine:3.6@sha256:66790a2b79e1ea3e1dabac43990c54aca5d1ddf268d9a5a0285e4167c8b24475 0.1s
=> CACHED [linux/s390x 2/2] ADD entrypoint.sh /usr/local/bin/entrypoint.sh 0.0s
=> CACHED [linux/amd64 2/2] ADD entrypoint.sh /usr/local/bin/entrypoint.sh 0.0s
=> CACHED [linux/arm/v7 2/2] ADD entrypoint.sh /usr/local/bin/entrypoint.sh 0.0s
=> CACHED [linux/386 2/2] ADD entrypoint.sh /usr/local/bin/entrypoint.sh 0.0s
=> CACHED [linux/arm/v6 2/2] ADD entrypoint.sh /usr/local/bin/entrypoint.sh 0.0s
=> CACHED [linux/arm64 2/2] ADD entrypoint.sh /usr/local/bin/entrypoint.sh 0.0s
=> CACHED [linux/ppc64le 2/2] ADD entrypoint.sh /usr/local/bin/entrypoint.sh 0.0s
=> exporting to image 24.5s
=> => exporting layers 0.0s
=> => exporting manifest sha256:307ce300e1b8b3f1db5f39e3154dfb066af07cbe252c2cec19fa3b24998bf69b 0.0s
=> => exporting config sha256:5fdc080798ebbc6274ecc6e83d99b289cec9d66c27fee20083949c3348879d0c 0.0s
=> => exporting manifest sha256:fa8cb5dd24efdd8e385531edcc51780d74611c713190b79d14d9987c7995ccf5 0.0s
=> => exporting config sha256:6005f9834969e5e1b8d3580309ccfd4b638d283c755fc1c20a1f0d333248cbdf 0.0s
=> => exporting manifest sha256:73de05890eefab2fd8ecc0f40a6a1851a03fde958183c0512cf317ac9bb0ae64 0.0s
=> => exporting config sha256:d6b77e3c1a8952de66fef24a2b9ccd1a3313bb9ea3e4d614f54a82f4f13afd0d 0.0s
=> => exporting manifest sha256:1a582ac387212b414f6c47e6f008e013bad47caaaf22180c0bef9ee976b0f688 0.0s
=> => exporting config sha256:718ce1322aeba3ad77847b03dab5736b10e0ceb2f7d29a2e59b6af301184a984 0.0s
=> => exporting manifest sha256:e94441ecfde85ad5df028a0a0d1c16a7e0a2cc4d690d2ad4daa188f58d2acdd9 0.0s
=> => exporting config sha256:b7e0735c53bcc17be8f452e2265f516dd73c66d0136d89e1b32e0fbc7ce895d7 0.0s
=> => exporting manifest sha256:8b960d90cf3d9e1516268cb9cb6b535dbeb26b445c4ca27b5db12c27e1906830 0.0s
=> => exporting config sha256:cb55cac155ba35569c2eb5b6cc86e26026e530fb326c3e92b7f6200ab7a91d14 0.0s
=> => exporting manifest sha256:dd0cddc2c52f2b10ed96f69113bbb2e566e493d8eb4a05e5cbd04ea682877674 0.0s
=> => exporting config sha256:2bd9d7416e4a278d123f58795d7a568f3825a05e6a6c97599acbea884db07d87 0.0s
=> => exporting manifest list sha256:302ae52206e96e45298e7e49c4122eca9752fb52f6e1ed61b28d39b2360de395 0.0s
=> => pushing layers 21.2s
=> => pushing manifest for docker.io/andrewlock/wait-for-dependencies:latest 3.0s
=> [auth] andrewlock/wait-for-dependencies:pull,push token for registry-1.docker.io 0.0s
=> [auth] andrewlock/docker-wait-for-dependencies:pull andrewlock/wait-for-dependencies:pull,push token for registry-1.docker.io
If you don't want to push directly to a container registry, you can use --load
to test it locally, but you can't test with multiple platforms yet unfortunately:
docker buildx build --load -t andrewlock/wait-for-dependencies --platform linux/arm64 .
And that's it! If you view the image on Docker hub you can see that the image supports multiple architectures under the OS/ARCH column:
With that change, we could now run the docker image on ARM64 processors!
Summary
In this post, I discussed Docker multi-arch images, and how they can be used to easily run an image on multiple processor architectures such as ARM 64. I then showed how you can use buildx
to build for multiple processor architectures using Docker Desktop. The whole process was incredibly simple and easy, and I quickly had a Docker container that would run on an ARM64 processor architecture!