blog post image
Andrew Lock avatar

Andrew Lock

~6 min read

Creating Docker multi-arch images for ARM64 from Windows

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 [email protected]: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 x64
  • linux/arm64: ARM 64
  • linux/riscv64: 64-bit RISC-V
  • linux/ppc64le: 64-bit little-endian PowerPC
  • linux/s390x: 64-bit Linux on IBM Z
  • linux/386: Linux x86
  • linux/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:

Multi-arch support for a docker image on Docker Hub

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!

Andrew Lock | .Net Escapades
Want an email when
there's new posts?