Multi-CPU architecture container images. How to build and push them on Docker Hub (or any other registry)
Tags: docker, multiarch, containers
Published: 2021-06-02 12:09:09

This is the second post in the series on how we prepared our application to run on M1 (Apple Silicon).

In the previous part we were talking about Go programming language and its ability to easily [cross-compile applications](https://dev.to/tidalmigrations/how-to-cross-compile-go-app-for-apple-silicon-m1-27l6) for different operating systems and CPU architectures using just a developer's laptop.

With this post, I'm going to describe some other aspects of modern cross-platform applications development.

## Our application

It's worth repeating that at [Tidal Migrations](https://tidalmigrations.com/) we build our CLI application — [Tidal Tools](https://tidalmigrations.com/tidal-tools/) — to make it easier for our customers to deal with all sorts of data necessary on their way towards the clouds. Tidal Migrations' [May 2021 Newsletter](https://tidalmigrations.com/2021-may-newsletter/#product-highlight) describes Tidal Tools as the

> meat-and-potatoes of how you’ll start your cloud journey.

The CLI app could be run anywhere:

Locally
* on Microsoft Windows
* on Apple macOS (Intel or M1)
* on GNU/Linux

Or preinstalled on a free cloud VM in:
* [AWS](https://aws.amazon.com/marketplace/pp/prodview-uicif637zwja2)
* [Azure](https://azuremarketplace.microsoft.com/en-us/marketplace/apps/tidal-migrations.tidal_tools)
* [Google Cloud Platform](https://console.cloud.google.com/marketplace/product/tidal-migrations-public/tidal-tools)

## Tidal Tools architecture in brief

Tidal Tools is a Go command-line interface (CLI) application. It mostly acts as a [Tidal Migrations API](https://guides.tidalmg.com/api-getting-started.html) client accompanying our [web application](https://get.tidalmg.com/). It also has some additional features like [source code](https://guides.tidalmg.com/analyze-source-code.html) and [database analysis](https://guides.tidalmg.com/analyze-database.html).

Those two extra functionalities are implemented by [our technological partners](https://tidalmigrations.com/technology-partners/) in other programming languages. 

We build Docker container images for such 3rd-party solutions and our application (Tidal Tools) runs containers under the hood using the awesome [Docker Go SDK](https://pkg.go.dev/github.com/docker/docker/client).

## Problems with container images

While preparing a new release of Tidal Tools for M1 Macs we discovered that our existing Docker container images won't work on the new Apple Silicon architecture. After some investigation we figured out that we build our container images for `amd64` architecture, while M1 Macs expect images for `arm64` CPU architecture.

Docker images can support multiple architectures, which means that a single image may contain variants for different architectures, and sometimes for different operating systems, such as Windows.

When running an image with multi-architecture support, `docker` automatically selects the image variant that matches your OS and architecture.

After some trial and error with our Docker images we are now finally confident in our happy path on how to build multi-CPU architecture Docker container images. In other words, we now know how to build container images for different architectures and push such images to container registries (e.g. Docker Hub) to be used on machines with different OSes and architectures. In short, build on (for example) Debian GNU/Linux — run on (for example) macOS for M1!

So, without further delay, let's jump straight to the topic!

## How to build multi-arch container images with `docker buildx`

**CAUTION!** To build container images with multi-CPU architecture support, you need to use [parent image](https://docs.docker.com/glossary/#parent-image) which supports multiple CPU architectures. Most of the official images on Docker Hub provide a [variety of architectures](https://github.com/docker-library/official-images#architectures-other-than-amd64). For example, the `openjdk` image variants (which we're going to use later) support `arm64v8` and `amd64`.

For this example, we're going to use “Hello world” application written in Java:

```java
// HelloWorld.java

public class HelloWorld {
  public static void main(String[] args) {
    System.out.println("Hello world!");
  }
}
```

Let's start with a `Dockerfile`:

```Dockerfile
# Dockerfile

FROM openjdk:8-jdk-slim AS builder
COPY HelloWorld.java /app/
WORKDIR /app
RUN javac HelloWorld.java

FROM openjdk:8-jre-slim
COPY --from=builder /app/HelloWorld.class /app/
WORKDIR /app
CMD ["java", "HelloWorld"]
```

To make it possible to build multi-CPU architecture container images we need to install [Docker Buildx](https://github.com/docker/buildx/). Docker Buildx is a CLI plugin that extends the `docker` command with some additional features, and multi-arch builds is one of those. If you're using recent Docker Desktop or Docker for Linux packages chances are high that Buildx is already available for you. If not, check the [installation instructions](https://github.com/docker/buildx/#installing).

Create and switch to using a new builder which gives access to the new multi-architecture features:

```
docker buildx create --name mybuilder --use
```

Log in to a Docker registry:

```
docker login
```

Build and push multi-arch container image for x86-64 (`amd64`) and AArch64 (`arm64`) CPU platforms (replace `your-username` with the actual Docker registry user name):

```
docker buildx build . \
  --platform linux/arm64,linux/amd64 \
  --tag your-username/hello-world:latest \
  --push
```

Running the above commands would build and push multi-arch container images to your Docker Hub profile:

![alt text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rqe289cmmn6fwj13qtn2.png) 

When running such image with multi-architecture support, `docker` automatically selects the image variant that matches the running OS and architecture.

That's it! With this simple trick you can build Docker container images for different operating systems and architectures and host such images on Docker Hub.

I hope you enjoyed this post! Stay tuned to learn more about  how we prepared our application to run on M1 (Apple Silicon)!