Create multi-architecture Docker images

Benjamin
5 min readMar 31, 2022

--

How can we support as many architectures as possible for our current project?

On both the development and deployment side, this question is becoming increasingly important, not least due to the growing adoption of Apple Silicon in dev machines and AWS instances that are based on the arm64 architecture rather than the traditional amd64. Today, let’s look together at how Docker can be used to create container images across platforms and offer them in a container registry.

  1. Multi-architecture Docker Manifests
  2. You can choose more than one way
  3. Creating an image with a build
  4. Potential challenges and limitations

Motivation
Before we jump in, let’s take a quick look at the prerequisites and motivations for tackling this topic.

On the developer side, multiple architecture support means hardware independence. This means that a MacBook user can also work on projects that were originally intended for Windows only without any problems. Last but not least, this also ensures a more diverse developer community that can create projects together.

The advantages don’t stop on the coding side, however. It also brings significant cost savings in the operation of cloud environments. One example of this is the Amazon EC2 A1 instances, which run on the specially developed 2nd generation Graviton chip and are based on the arm64 architecture. According to Amazon, these bring a cost saving of up to 40% in direct comparison to amd64 instances.

To finally test these theses, let’s now look under the Docker hood to understand how images are created for each platform and bundled in Docker Hub.

Multi-architecture Docker Manifests
But wait. You should already be familiar with how a general image is built and pushed to a registry.

Now that you know how to push a single image, you may already be asking yourself how to upload multiple images for different platforms and then bundle them so that the later user always gets exactly the right image from a central pull command.

The solution here is the manifest that exists for each container repo. Let’s take a look at what such a manifest looks like for the popular golang image, for example:

docker manifest inspect golang:latest

Overview of the supported platforms of the golang-image
You can see in the blue boxes which architectures the golang image supports. If you now run docker pull golang:latest in your console, Docker will automatically pick the appropriate image for your platform.

Many ways to get to the goal

To get our project running on the platforms that matter to us, there are two ways:

The manual way involves some work and requires that you have a device available for building the image for each platform you want to support.

The second way runs through a Docker feature called buildx, which can be run with a console command and even requires only your computer to compile for the different platforms.

For now, let’s go through the manual path to develop a better understanding of exactly what steps are executed in the background.

First and foremost is building the image with the appropriate architecture tag on each device that will be supported. Once this process is complete, we then create our own manifest for our repo using the following pattern:

docker manifest create my-image:latest \
my-image:amd64 \
my-image:arm64

To publish this manifest, the last thing to do is to push it to your registry:

docker manifest push my-image:latest

Overview multi-arch docker images

Now that we know the manual way, the question follows, “Isn’t there an easier way?”

The answer to that is, “Yes! And significantly!”

The second way to deploy images to different platforms this time using only one Dockerfile is through buildx. If you still have an older version of Docker installed, this feature might not even be available to you yet, or it might be listed as an experimental feature.

In principle, it is also possible to create a build farm with different architectures, which are called “nodes” in this context. However, at this point, I would like to focus on the simplest way to use buildx. Namely, it requires only one computer.

How to make it work? With QEMU! QEMU is an open-source software integrated into buildx, which allows for the emulation of other architectures on one system. Thus, images can be built that do not correspond to the native architecture.

Let’s take a look at this process as well!

Creating an image with buildx

In the beginning, the default builder for buildx is used by default.

docker buildx ls

NAME/NODE DRIVER/ENDPOINT STATUS PLATFORMS
default * docker
default docker running linux/arm64, linux/amd64,
docker buildx ls

However, it is good practice to create a new builder. If something goes wrong, you can simply delete your builder and reconfigure it.

docker buildx create — name mybuilder

docker buildx use mybuilder

Let’s take a closer look at the builder we just created and then at the one we selected.

docker buildx inspect mybuilder — bootstrap

Name: mybuilder
Driver: docker-container
Nodes:
Name: mybuilder0
Endpoint: unix:///var/run/docker.sock
Status: running
Platforms: linux/arm64, linux/amd64, …

We can see at this point under “Platforms” that the current builder supports both amd64 and arm64 and even some more platforms.

Now that we’re all set to use buildx, let’s try out the magic:

docker buildx build \
— platform linux/arm64,linux/amd64 \
-t repo/my-image:latest \
. \
— push

With — platform we specify that only images for the two platforms amd64 and arm64 should be built.

— push pushes, once the images are ready, into your registry and builds the manifest in the same move. So we don’t have to do anything but wait until everything is done and uploaded.

Possible challenges and limitations
Not every base image supports multiple platforms. Given the ever-growing demand, this issue will diminish over time.
Not all dependencies of your codebase may be supported on all platforms. If this is the case for your project, build them yourself.
There are projects where the software needs to be handled differently depending on the architecture and thus requires its own Dockerfiles in each case. Here you use the manual way because buildx is not intended for this case.

And that’s it! You can now build Docker images that allow you and your colleagues to support a colorful selection of devices during development and possibly even use the Gravitation chip’s savings potential on the fly to your advantage!

--

--

Benjamin
Benjamin

Written by Benjamin

Visit me on: Codegrepper or on Github

No responses yet