Building Multi-Architecture Containers with Buildah

Tim Clegg
Oracle Developers
Published in
6 min readJun 16, 2022
Photo by Tom Fisk: https://www.pexels.com/photo/aerial-photography-of-container-van-lot-3063470/
Photo by Tom Fisk: https://www.pexels.com/photo/aerial-photography-of-container-van-lot-3063470/

I’ve been on a kick (some would call it a series) lately, looking at how to successfully build multi-architecture containers. First we looked at the high-level concepts involved, then dove into how to use Docker to build multi-architecture containers. Next up we looked at how to do the same thing using Podman. Now we’re going to look at how to do this with yet another tool: Buildah. Why, you ask? Because we can! No, really, having multiple tools available can be beneficial as we might have preferences or requirements to use one tool over another. There are certainly other ways to build images, but we won’t be delving deeper into the subject (at least at this time) beyond this last post in the series.

Prerequisites

If you’d like to follow along, you’ll need an Oracle Cloud Infrastructure (OCI) account and an OCI Container Repository (OCIR) called hello-world. See the OCI docs for more info on how to create a repository.

For this scenario, I’ve used Buildah v1.23.1 running on Oracle Linux 8.5. To see the version of Oracle Linux you are running, take a look at the output of cat /etc/os-release. Different versions might result in different results.

The tools we’ve looked at previously (Docker and Podman) both have pretty decent support for different operating systems. Buildah unfortunately is an “exclusively Linux” tool. You might want to follow along by running this on an Oracle Linux OCI instance.

Getting Started

To start things off, we need to understand how to authenticate (login and logout) of OCIR using Buildah. Logging in (authenticating) to OCIR should look familiar to what we’ve done with other tools:

$ buildah login phx.ocir.io

You’ll need to use the correct region hostname for the OCI region you’re using. See the region availability for region-specific hostnames. The OCI Container Registry documentation talks about this in greater detail, as well as the region keys in the OCI documentation.

Make sure to use the proper format for the username you provide. The username should be in the format of <namespace>/<username> (or if you’re using IDCS, <namespace>/oracleidentitycloudservice/<username>). The password will be an Auth Token associated with your account. If this seems a bit foreign or confusing, take a look at the OCI documentation for more details.

Logging out of OCIR is easily done with:

$ buildah logout phx.ocir.io

Now that we can interact with OCIR, let’s learn how to build multi-arch containers using Buildah!

Building the Containers

As with previous articles, we’ll keep to a short and simple Dockerfile:

FROM container-registry.oracle.com/os/oraclelinux:8-slim

CMD echo "Hello world!"

This is intentional. We need to keep the focus on container building, not language-specific complexities.

We’ve done this in prior articles, but to be thorough, we’ll check to make sure that the base image is available for both architectures that we’ll be building for (arm64 and amd64). Looking at the Oracle Container Registry for this container (you’ll need to click on OS, then on oraclelinux to see what I’m showing below), we can see that there are two platforms supported (both of which we’re wanting to build against):

Always check to make sure that you have a base image for the the different target architectures you’ll be building against!

There are two ways to build container multi-arch container images when using Buildah. I’ll call it the “easy path” (batched) and the manual method.

So I don’t spoil your “appetite” with the batched (easy) method, let’s look at how to do things manually first.

Manual Method

Building the Containers

We begin by creating two container images (one for each architecture: arm64 and amd64):

$ buildah build --pull --platform linux/arm64/v8 -t phx.ocir.io/<namespace>/hello-world:v1.0.0-linux-arm64 .
<omitted for brevity>
$ buildah build --pull --platform linux/amd64 -t phx.ocir.io/<namespace>/hello-world:v1.0.0-linux-amd64 .
<omitted for brevity>

The — platform argument is used to specify the desired target architecture (the architecture that the container will be built for). A platform is typically formed with the operating system, CPU architecture and an architecture variant (which is optional and often times excluded), with each being separated by a forward-slash (/).

Let’s inspect the the containers we just built, looking for the architecture to make sure it’s correct:

$ buildah build --pull --platform linux/amd64 -t phx.ocir.io/<namespace>/hello-world:v1.0.0-linux-amd64 .
<omitted for brevity>
$ buildah inspect phx.ocir.io/<namespace>/hello-world:v1.0.0-linux-arm64 | grep \"architecture\"
"architecture": "arm64",
"architecture": "arm64",
$ buildah inspect phx.ocir.io/<namespace>/hello-world:v1.0.0-linux-amd64 | grep \"architecture\"
"architecture": "amd64",
"architecture": "amd64",

What we see from the above is that both containers we’ve built have the correct architecture. Terrific!

It’s time to push the images to the OCI Registry (OCIR):

$ buildah push phx.ocir.io/<namespace>/hello-world:v1.0.0-linux-arm64
<omitted for brevity>
$ buildah push phx.ocir.io/<namespace>/hello-world:v1.0.0-linux-amd64
<omitted for brevity>

With our images pushed to OCIR, we’re able to run them. The thing is, we don’t want to have to call out each variant for the execution environment CPU architecture. In other words, if I’m on an OCI A1 (Arm) instance, I don’t want to have to say docker run phx.ocir.io/<namespace>/hello-world:v1.0.0-linux-arm64 and when on an x86_64 (aka amd64) OCI instance (such as an E4 instance shape) run docker run phx.ocir.io/<namespace>/hello-world:v1.0.0-linux-amd64. This is a bit messy. It would be unmanageable in a Kubernetes (K8s) manifest. Instead, I want to be able to say docker run phx.ocir.io/<namespace>/hello-world:v1.0.0 and let it get the right image for the architecture that’s going to run the container. This is what a manifest solves.

Creating the Manifest

Making a manifest is pretty straightforward with Buildah:

$ buildah manifest create phx.ocir.io/<namespace>/hello-world:v1.0.0 \
phx.ocir.io/<namespace>/hello-world:v1.0.0-linux-arm64 \
phx.ocir.io/<namespace>/hello-world:v1.0.0-linux-amd64
<omitted for brevity>
$ buildah manifest push phx.ocir.io/<namespace>/hello-world:v1.0.0 docker://phx.ocir.io/<namespace>/hello-world:v1.0.0
<omitted for brevity>
$ buildah manifest rm phx.ocir.io/<namespace>/hello-world:v1.0.0
<omitted for brevity>

The first command creates the manifest (hello-world:v1.0.0), adding two containers to it (hello-world:v1.0.0-linux-amd64 and hello-world:v1.0.0-arm64).

In the second command Buildah pushes the newly made manifest to OCIR.

Lastly, to keep things clean, we remove the local (cached) copy of the manifest.

To validate that the manifest looks good, let’s inspect it:

$ buildah manifest inspect phx.ocir.io/<namespace>/hello-world:v1.0.0
{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
"manifests": [
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"size": 722,
"digest": "sha256:abc123...",
"platform": {
"architecture": "arm64",
"os": "linux"
}
},
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"size": 722,
"digest": "sha256:xyz123...",
"platform": {
"architecture": "amd64",
"os": "linux"
}
}
]
}

It’s great to have a way to do this by hand in case it’s needed (or wanted). Let’s try the batched, or as I like to call it, the “easy” method.

Batched (Easy) Method

Buildah can take care of building the containers for each desired target platform as well as the manifest, all in a single command:

$ buildah build --jobs=2 --platform=linux/arm64/v8,linux/amd64 --manifest phx.ocir.io/<namespace>/hello-world:v1.0.2 .
<omitted for brevity>

Next we need to push this to OCIR:

$ buildah push phx.ocir.io/<namespace>/hello-world:v1.0.2
<omitted for brevity>

While not shown here, if you’d like to see the images that were built (think of this as “extra credit”), look at the output of buildah images –all.

For grins, we can take a look at the manifest:

$ buildah manifest inspect phx.ocir.io/<namespace>/hello-world:v1.0.2
{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
"manifests": [
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"size": 715,
"digest": "sha256:abc123...",
"platform": {
"architecture": "amd64",
"os": "linux"
}
},
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"size": 715,
"digest": "sha256:xyz123...",
"platform": {
"architecture": "arm64",
"os": "linux",
"variant": "v8"
}
}
]
}

The platforms look great! How easy was that?

Conclusion

While Buildah isn’t the most portable tool (really for Linux), it is quite powerful and can make life really easy. Being able to build the containers and the manifest in a single command is powerful.

This series did not attempt to exhaust every nook-and-cranny of the tools we’ve examined (or the topic of multi-architectural containers). I do hope that you’ve found it helpful in understanding the basics of building and working with containers in a multi-architectural world.

In closing this series, I’d like to call out a special thanks to my colleague Ashu who’s been a big help in peer-reviewing this series of articles for me. Until next time, keep the bits flowing!

You can always try this out on our free tier, of course.

Want to discuss? Join the conversation on our public Slack channel!

--

--

Oracle Developers
Oracle Developers

Published in Oracle Developers

Aggregation of articles from Oracle engineers, Groundbreaker Ambassadors, Oracle ACEs, and Java Champions on all things Oracle technology. The views expressed are those of the authors and not necessarily of Oracle.

Tim Clegg
Tim Clegg

Written by Tim Clegg

Polyglot skillset: software development, cloud/infrastructure architecture, IaC, DevSecOps and more. Employed at Oracle. Views and opinions are my own.