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!

Podman - Multi-Arch Images
Photo by Pixabay

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.

Podman - Images
Podman is a daemonless container engine and getting started is quite easy. Running a container may be nice for some experimenting or deployments, but most of us want to add tools to the container and customize them to our needs. This can be done with container images.
Container - Smaller Images
Building your own containers is very easy and fast to learn. But very soon, you may end up with large container images and no idea how to reduce the footprint. Since you need to download and store each image, it can be useful to minimize the images, and this article will address exactly this.
Podman - Encrypted Images
The world of containers consists of images, which are made out of layers. Normally, everybody with access to the image can investigate it, check out what’s in there and how it works. Encrypting your images can prevent others to see the content of your images, even if they get access to it.

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:

Supporting building multi-platform images (podman buildx) · Issue #3063 · containers/podman
/kind feature Description Supporting building multi-platform images (podman buildx) Detail This ticket is a request for feature. docker buildx [1][2] is to enable building and running multi-platfor…

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.

Supporting building multi-platform images (podman buildx) · Issue #1590 · containers/buildah
Description Supporting building multi-platform images (podman buildx) Detail This ticket is a request for feature, originally from containers/podman#3063 . docker buildx [1][2] is to enable buildin…

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.

package main

import (
	"fmt"
	"net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello builder")
}

func main() {
	http.HandleFunc("/", handler)
	http.ListenAndServe(":8080", nil)
}

hello.go

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.

  1. Create Containerfiles/Dockerfiles
  2. Initialize a manifest
  3. Build images
  4. 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.

FROM docker.io/library/golang:1.22 AS builder

COPY . .

RUN go build -o /build/hello hello.go

FROM scratch

COPY --from=builder /build/hello /usr/local/bin/hello

CMD ["/usr/local/bin/hello"]

Containerfile

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.

Before leaving you with the above, let me share some links that might be interesting to you.

podman-build — Podman documentation
podman-manifest — Podman documentation
How to build multi-architecture container images | Red Hat Developer
This article demonstrates how to build multi-architecture container images such as Linux AMD64.
Building Multi-Architecture Containers on OCI with Podman
How to build multi-architecture (arm64 and amd64) container images on OCI using Podman.
How to specify the CPU architecture when pulling images with Podman
If you’re building multi-architecture solutions, you need to know how container image architectures work, how to pull them, and how to run containers on different systems using Podman.

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.