Why did I write this stuff?

Setting up docker builds for multiple platforms or architectures is not very complicated, but at least I found the instructions on the Internet scarce, lacking and somehow confusing.

If you want to build multiarch/multiplatform container images with docker on your local machine, with buildx and qemu and so forth, this document describes a fast track to accomplish it. I’ll describe all steps a bit, but to be honest I don’t know much more about those steps either at this point.

My instructions are applicable for Ubuntu 22.04, but should work if slightly adjusted for other distros as well. I’ll be building for amd64 and arm64.

Set up a docker ubuntu repository and install packages

There are ubuntu specific docker setup instructions, but I’ll copy & paste the relevant bits here anyway.

First remove any possibly installed OS docker packages to avoid conflicts:

$ for pkg in docker.io docker-doc docker-compose podman-docker containerd runc; do sudo apt-get remove $pkg; done

Add the docker apt repository and gpg key:

$ sudo apt-get update
$ sudo apt-get install ca-certificates curl gnupg
$ sudo install -m 0755 -d /etc/apt/keyrings
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
$ sudo chmod a+r /etc/apt/keyrings/docker.gpg
$ echo \
  "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

Then install docker packages:

$ sudo apt-get update
$ sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Enable buildkit in docker daemon

Create or edit your /etc/docker/daemon.json to contain the following:

{
    "features": {
        "buildkit": true
    }
}

Restart your docker daemon:

$ sudo systemctl restart docker

Start a registry container

Docker image cache doesn’t understand multiplatform images, so we’ll need something that is more like a real registry. So install one as a container:

$ docker run -d -p 5000:5000 --name registry --network host registry:2

Create docker contexts for builds

The convention seems to be that one has to create a docker context for each “builder node”. I’ll create a single context, and a single builder node per platform. So:

$ docker context create builder-amd64
$ docker context create builder-arm64

Check that you have things approximately like this:

$ docker context ls
NAME            DESCRIPTION                               DOCKER ENDPOINT                             ERROR
builder-amd64   Current DOCKER_HOST based configuration   unix:///var/run/docker.sock
builder-arm64   Current DOCKER_HOST based configuration   unix:///var/run/docker.sock
default *       Current DOCKER_HOST based configuration   unix:///var/run/docker.sock

Create buildx builders for both platforms

Create a config.toml file with the following content:

[registry."localhost:5000"]
   http = true
   insecure = true

Next we’ll create a buildx builders and add two nodes under it:

$ docker buildx create --use --bootstrap --name builder \
         --node amd64 --platform linux/amd64 \
         --driver-opt network=host \
         --config config.toml builder-amd64
$ docker buildx create --append --bootstrap --name builder \
         --node arm64 --platform linux/arm64 \
         --driver-opt network=host \
         --config config.toml builder-arm64

Check buildx builders and nodes

$ docker buildx ls
NAME/NODE       DRIVER/ENDPOINT  STATUS  BUILDKIT                              PLATFORMS
builder *       docker-container
  amd64         builder-amd64    running v0.11.6                               linux/amd64*, linux/amd64/v2, linux/amd64/v3, linux/386
  arm64         builder-arm64    running v0.11.6                               linux/arm64*, linux/amd64, linux/amd64/v2, linux/amd64/v3, linux/386
builder-amd64   docker
  builder-amd64 builder-amd64    running v0.11.7-0.20230525183624-798ad6b0ce9f linux/amd64, linux/amd64/v2, linux/amd64/v3, linux/386
builder-arm64   docker
  builder-arm64 builder-arm64    running v0.11.7-0.20230525183624-798ad6b0ce9f linux/amd64, linux/amd64/v2, linux/amd64/v3, linux/386
default         docker
  default       default          running v0.11.7-0.20230525183624-798ad6b0ce9f linux/amd64, linux/amd64/v2, linux/amd64/v3, linux/386

Build something

Now instead of the usual docker build you’ll do docker buildx build like so:

$ docker buildx build --platform linux/amd64,linux/arm64 -t localhost:5000/YOURIMAGENAMEHERE:WHATEVERTAGHERE --push .

You will need to push the results to a “real” repository, so the image name has to have the repository location as a prefix, and the build results must be pushed to a real registyr, hence the “–push” switch.

You can inspect the buildresult like this:

$ docker buildx imagetools inspect localhost:5000/YOURIMAGENAMEHERE:WHATEVERTAGHERE

.. and the output should indicate that the image has manifests for all built platforms.

Run non-native platform container images through QEMU emulation

Install QEMU and binfmt support

$ sudo apt-get install qemu binfmt-support qemu-user-static

Run some magic scripts to enable emulation:

$ docker run --rm --privileged multiarch/qemu-user-static --reset -p yes

Run some containers with different platforms:

$ docker run --platform linux/arm64 -it ubuntu:22.04 uname -m
[..]
aarch64
$ docker run --platform linux/amd64 -it ubuntu:22.04 uname -m
[..]
x86_64

So there you have it. Multiarch builds plus the ability to run alien architecture images with emulation.