Podman - Multi-Arch Images
Have you built a container image on your workstation, just to find out that it doesn't work on your Raspberry Pi? Maybe your project considers publishing a software for arm64 and amd64, so your users can run it on different platforms? No worries, Podman got you covered!
Have you built a container image on your workstation, just to find out that it doesn't work on your Raspberry Pi? Maybe your project considers publishing a software for arm64 and amd64, so your users can run it on different platforms? No worries, Podman got you covered! With multi-arch builds, you can build your image once and run them on multiple platforms.
Let's build cross-platform/multi-arch container images.
Podman
Podman is a rootless and daemonless drop-in replacement for Docker. You can start and stop container, build and push images, and basically everything you can do with Docker.
There are some huge benefits, when it comes to Podman. It is a systemd native, which means you can control your containers with systemd services easily. It has various options to run containers as root or user. Not only that, but it also provides features that don't even exist in Docker, like auto-updates, running Pods and even Kubernetes deployments.
You can find a couple of articles in my blog, too.
Podman Image builds
If you ran into this article, and you don't have any idea about Podman image builds, I strongly recommend reading some basic articles, first. In the past, I tackled image builds in the below linked blog posts, already.
Multi-Arch builds
In the past, we just had amd64. Not really, but amd64 (aka Intel or AMD CPUs) was the majority on servers, laptops, workstations and in the cloud. Nowadays, you will see ARM based CPUs way more often. Apple Silicon, Cloud providers, Edge computing and small devices like the Raspberry Pi can handle arm64 builds.
Yes, containers can run on basically every Linux. Yet, your application might need cross-compilation to run on arm64, the same can be said about the dependencies in a container. Therefore, container images are provided for multiple platforms these days.
With Docker, we got docker buildx
, but what about Podman? Is there an equivalent to build multi-arch images on Podman?
Some history
Podman wasn't able to build cross-platform/multi-arch for quite some time. This also hit me, since I was forced to set up build machines with different tooling or even build container images on a different host.
The community was eager to get this feature since 2019:
This lead to implementations in Buildah (I haven't had an article about it, right?). The work was finally done in 2021 and included lots of new things, like support for different architectures in general.
Nowadays (meaning, since 2021), we are able to create multi-arch container images. So, let's see how this works.
Go multi-arch container
I think, Go is a good example for testing out multi-arch images. It supports cross-platform compilation, and you can build static binaries without any dependencies. Therefore, we don't have to take care if a distribution supports our desired architecture or if some libraries are different.
Our Code
For the sake of this tutorial, let's create a super simple go program. In case you don't have installed Go, yet, please check out "Go - Getting Started", beforehand. Anyway, we will create a simple Go file.
We will also need to initialize the directory, so Go can handle the repositories.
# Initialize project
$ go mod init gitlab.com/dschier/podman-multiarch
# Check the created mod file
$ cat go.mod
module gitlab.com/dschier/podman-multiarch
go 1.22.0
And finally, we can spin it up locally.
# Start without compiling
$ go run hello.go
And you can point your browser to http://localhost:8080, to see our fresh application output. From this point onwards, you can build your binaries with Go for different architectures. For example, you can create a binary for amd64 and arm64.
# Build the binary
$ go build -o build/hello hello.go
# Show the output
$ ls build/
hello
But, since you want to have multi-arch builds, it might be more something like:
# Build for arm64
$ GOARCH=arm64 GOOS=linux go build -o build/hello-$GOOS-$GOARCH hello.go
# Build for amd64
$ GOARCH=amd64 GOOS=linux go build -o build/hello-$GOOS-$GOARCH hello.go
# Show the output
$ ls build/
hello-linux-amd64 hello-linux-arm64
Building binaries is all good and nice, but how to publish each of the properly as container image?
Container builds
Building multiple architectures requires putting in slightly more effort than just running podman build
. But, it's pretty straightforward.
- Create Containerfiles/Dockerfiles
- Initialize a manifest
- Build images
- Optionally: Push manifest
Don't worry, its really easy.
Let's start with the Containerfile. In our case, we will need only one, but there might be other languages or situations where you need to maintain multiple files. For Go, it is also recommended to have multi-stage builds. The resulting Containerfile will look like the below.
In the first stage, we will use a Golang container to build the binary. The second stage will copy the resulting binary into a scratch (meaning empty) container image and add the command.
Building an image with Podman would be possible now. But, we need to tell Podman, that e will have multiple images that are meant for the same "artifact". To handle this situation, Docker Inc. came up with manifests. A manifest is a JSON expression that describes a set of images for different platforms. Podman has some built-in tooling to create and inspect manifests.
# Create a manifest
$ podman manifest create hello
17a63042361aa2a8d5524a54e8b487d07734166394430f4f185d33d1d8d605ae
# A manifest is also shown in the "images" view
$ podman image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
localhost/hello latest 17a63042361a 34 seconds ago 110 B
# Inspect the manifest
$ podman manifest inspect localhost/hello:latest
{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
"manifests": []
}
So, a manifest is basically existing for each image, but now we are massaging it manually to store multiple images in it.
The command, to build both architectures in one shot, is also super simple. But, we need to tell Podman which manifest it should use for storing.
# Build multi-arch image
$ podman build --platform linux/amd64,linux/arm64 --manifest localhost/hello .
[linux/arm64] [1/2] STEP 1/3: FROM docker.io/library/golang:1.22 AS builder
...
[linux/amd64] [1/2] STEP 1/3: FROM docker.io/library/golang:1.22 AS builder
83913b02346ff007ff04c846fac9043411aa2b5f7af083ad018fcd1310925d17
...
The above will take some time, since each image is build in series and not parallel. The result is pretty stunning, though.
# Show images
$ podman image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
localhost/hello latest 17a63042361a 21 minutes ago 1.05 kB
# Inspect the manifest
$ podman manifest inspect localhost/hello:latest
{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
"manifests": [
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"size": 498,
"digest": "sha256:04c4d81008e5da630a9a66b2f50198f64517965d8d42bfc72495abb34991da7d",
"platform": {
"architecture": "arm64",
"os": "linux"
}
},
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"size": 498,
"digest": "sha256:0faefedcbe001076cb6cbe244ac9a41380ff89af71aa22ca0f2c14b4b275f5b0",
"platform": {
"architecture": "amd64",
"os": "linux"
}
}
]
}
Voilà, a manifest that references two images. Finally, we just need to push the images to our desired registry. Instead of pushing the images individually, we can push the manifest.
# Push the manifest and included images
$ podman manifest push localhost/hello:latest docker://quay.io/myuser/image:latest
And that's already it. Pull the image on your Raspberry Pi or another ARM machine. Podman also supports many more architectures, ranging from arm, arm64, 386, amd64, ppc64le to s390x. And all of this, regardless of the host system.
Docs & Links
Before leaving you with the above, let me share some links that might be interesting to you.
Conclusion
Building multi-arch is pretty easy, isn't it? No special tooling needed, no additional VMs, and the option to run your cool software on more than Intel/AMD. Maybe you are using multi-arch builds already, but with a different tool? Do you consider testing Podman for the same? Are you using it already?
Let me know what you are doing with it and how it went. I would love to add more to it, based on your ideas.