Multi-Architecture Build Container Images
It used to be that you could get away with building your container images with only amd64 in mind. However, with the adoption and growth of arm-based devices we can no longer focus only on a single architecture. Luckily building cross architecture images requires little to no preparation. Both Docker and Podman provide a solution to this problem.
Requirements
For Docker and Docker Desktop, you’ll need to ensure that you’re using containerd. For the most part, if you’re on Linux then you’ll be using containerd already.
Docker Desktop
According to the Docker documentation, Docker Desktop has been transitioning to contianerd image store. To verify that Docker Desktop is using containerd
- Open Docker Desktop
- Navigate to Settings in Docker Desktop.
- In the General tab, check Use containerd for pulling and storing images.
- Select Apply.
Docker Engine
For Docker Engine add the following to /etc/docker/daemon.json
{
"features": {
"containerd-snapshotter": true
}
}
Podman
There isn’t anything that you need to do.
Build Strategies
There are three strategies for building images for other architectures, QEMU, native nodes aka cross-platform, and cross-compilation. The simpletest way is using emulation through QEMU. There is no additional configuration that is needed when using Docker Desktop because Docker Desktop uses a VM that already has QEMU configured.
QEMU
For Docker Engine you’ll need a few things first.
- Kernel 4.8 or later
- binfmt-support 2.17 package installed
- QEMU binaries must be statically compiled and registered with the fix_binary flag
Once the requirements have been met, you can install QEMU inside a container:
docker run --privileged --rm tonistiigi/binfmt --install all
Cross-platform
Cross-platform building is best for when you are building an image that’s more complicated, requires better performance, or cross-compiling isn’t an option. For this strategy we’ll need to have different contexts for different architectures. Contexts are a way for users to manage multiple Docker daemons from a single host.
docker context ls
NAME DESCRIPTION DOCKER ENDPOINT ERROR
default * Current DOCKER_HOST based configuration unix:///var/run/docker.sock
docker context create amd64-node --docker host=ssh://paulus@amd64-node
amd64-node
Successfully created context "amd64-node"
docker buildx create --use --name amd64-builder amd64-node
Cross-compiling
Cross-compiling is another option that you can use to build different binaries based on the architecture within the container. When using this method you can use the BUILDPLATFORM and TARGETPLATFORM variables within your Dockerfile.
FROM --platform=$BUILDPLATFORM golang:alpine AS build
ARG TARGETOS
ARG TARGETARCH
WORKDIR /app
ADD https://github.com/dvdksn/buildme.git#eb6279e0ad8a10003718656c6867539bd9426ad8 .
RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o server .
FROM alpine
COPY --from=build /app/server /server
ENTRYPOINT ["/server"]
In the example above, go is compiling the program for a $TARGETOS and $TARGETPLATFORM within the build process.
Process
When building multi-architecture container images the basic process is as follows.
- Create a Dockerfile for each architecture you wish to support.
- Build the image using the
buildorbuildx buildsub-commands for each of the target architectures by specifying the appropriate--platformoption. - Tag the images.
- Push the images
- Create a manifest list that references the different architecture-specific images.
- Push the manifest
QEMU with Docker
vim Dockerfile
docker buildx build --platform linux/amd64,linux/arm64 --output "type=image,push=true" --tag registry.paulslinuxbox.net/myapp:tag .
If there are no existing builders then you can create one by running docker builder create --use ---name docker_builder.
Cross-platform with Docker
vim Dockerfile
docker context create amd64-node --docker host=tcp://amd64-node:2375
docker context create arm64-node --docker host=tcp://arm64-node:2375
docker buildx create --use --name multi-arch amd64-node
docker buildx create --apend --name multi-arch arm64-node
docker buildx build --platform linux/amd64,linux/arm64 --output "type=image,push=true" --tag registry.paulslinuxbox.net/myapp:tag
Podman
vim Dockerfile
podman system connection add amd64-node --identity ~/.ssh/id_rsa ssh://192.168.1.2/path/to/podman.sock
podman system connection default amd64-node
podman manifest create myapp
podman build --platform linux/amd64,linux/arm64 --manifest myapp --output "type=image,push=true" --tag registry.paulslinuxbox.net/myapp:tag .
podman manifest push myapp:tag
Conclusion
That concludes building images for different architectures. As you can see creating container images for different architectures isn’t difficult. Although you can use QEMU without issues, if possible build the container image on the native architecture.