Recently, I have fallen in love with the Fedora distro.
And here you have bundled podman
as a container engine.
The nice feature of the podman
engine is that it is daemonless and allows you
to run containers as a root as well as in rootless mode.
Podman engine has Docker-compatible command line interface and has some nice features that have not presented (yet?) in Docker: generating a systemd service files or Kubernetes Pod YAML.
One of the things I miss a lot personally is seamless multi-architecture container
builds you have with Docker buildx
command.
Below I would like to share a way I perform such builds with podman
and friends.
First let specify system requirements:
podman
buildah
-
qemu-user-static
(for multi-arch builds forarm64
,arm64/v8
,armhf
, etc.)
Fedora/RHEL/CentOS
# dnf frontend
$ sudo dnf install podman buildah qemu-user-static
# yum frontend
$ sudo dnf install podman buildah qemu-user-static
Ubuntu/Debian
$ sudo apt-get update && sudo apt get install podman buildah qemu-user-static
Starting from Podman v3.4.4+
you will be able to run all commands with podman, while
early versions of the engine most likely require to switch to buildah
.
Building a multi-arch image
With Docker buildx
command building such an image is as easy as:
$ docker buildx build --push \
--tag your-username/multiarch-example:latest \
--platform linux/amd64,linux/arm/v7,linux/arm64 .
That is, specify a tag for your image (--tag <image/tag>
) and list platforms
you would like to build an image for (--platform <arch1,arch2,...>
).
With Podman I ended up with a script that looks like that:
#!/bin/bash
if [[ -z "${VERSION_TAG:+x}" ]]; then
echo "Please set up VERSION_TAG variable"
exit 2
fi
if [[ -z "${REGISTRY:+x}" ]]; then
echo "Please set up REGISTRY variable"
exit 2
fi
if [[ -z "${USER:+x}" ]]; then
echo "Please set up USER variable"
exit 2
fi
# Publish flag
if [[ "$#" -eq 1 ]] && [[ "$1" == "-p" ]]; then
SHOULD_PUBLISH=1
fi
# Manifest name
MANIFEST_NAME="image-multiarch"
# Build specific variables
SCRIPT_PATH="$(dirname -- $(readlink -f -- "$0"))"
BUILD_PATH="$(dirname -- ${SCRIPT_PATH})"
REGISTRY="$REGISTRY"
USER="$USER"
IMAGE_NAME="image"
IMAGE_TAG="${VERSION_TAG}"
# Base image name
BASE_IMAGE_NAME="${REGISTRY}/${USER}/${IMAGE_NAME}:${IMAGE_TAG}"
# Create a multi-architecture manifest
podman manifest create ${MANIFEST_NAME}
for arch in amd64 arm64; do
ARCH_FLAGS=("--arch" "${arch}")
if [[ "$arch" = "arm64" ]]; then
ARCH_FLAGS=("${ARCH_FLAGS[@]}" "--variant" "v8")
fi
podman build \
-t "$BASE_IMAGE_NAME-$arch" \
--manifest "${MANIFEST_NAME}" \
"${ARCH_FLAGS[@]}" "${BUILD_PATH}"
done
# Publish images to the registry
if [[ "$SHOULD_PUBLISH" -eq 1 ]]; then
podman push --all "${MANIFEST_NAME}" \
"docker://$BASE_IMAGE_NAME"
fi
Let’s say you save this script as a build.sh
file. Then you should call it
like that:
$ chmod +x ./build.sh
$ VERSION_TAG=0.1 REGISTRY=docker.io USER=awesomeuser ./build.sh
Although the script is far bigger than Docker buildx
command line, it is pretty
straightforward:
- Create Docker/OCI manifest file to manipulate with:
$ podman manifest create ${MANIFEST_NAME}
- For each architecture listed build an image with architecture specific tag and add that image into the manifest:
for arch in amd64 arm64; do
ARCH_FLAGS=("--arch" "${arch}")
if [[ "$arch" = "arm64" ]]; then
ARCH_FLAGS=("${ARCH_FLAGS[@]}" "--variant" "v8")
fi
podman build \
-t "$BASE_IMAGE_NAME-$arch" \
--manifest "${MANIFEST_NAME}" \
"${ARCH_FLAGS[@]}" "${BUILD_PATH}"
done
Here you need the following flags:
-
--manifest
: specifies a manifest name where an image will be added to after build is successful -
--arch
/--variant
: specifies architecture and architecture variant to build image for. The most recent Podman versions support--platform
flag where you may specify architecture, architecture variant and OS all at once ---platform=linux/arm64/v8
. For Podman<4
you most likely have to use--arch
/--variant
/--os
flags
- Publish the image
$ podman push --all "${MANIFEST_NAME}" "docker://$BASE_IMAGE_NAME"
Here we specified docker://
before an image name to tell that we want to use the Docker
transport to transfer an image. Red Hat has a great article about other transports. So that is a point to improve the script
by providing a transport method.
Keep in mind that the publishing step will require you to be logged in with a registry you want to push. To login you can use the following command:
# can provide --user <user> --password <passwd> flags to avoid that
# data prompting
$ podman login <REGISTRY>
Buildah usage
The same can be done with the buildah
command. podman
usage with manifest and push stuff
can be substituted with the buildah
as as:
$ buildah manifest create ${MANIFEST_NAME}
$ buildah push --all "${MANIFEST_NAME}" "docker://$BASE_IMAGE_NAME"
And build command should be updated with the buildah bud
(bud
stands for
build-using-dockerfile
):
$ buildah bud -t <tag> ...
Pretty easy, right?
Podman buildx support
Starting Podman 3.4.0+
there is buildx
support (not sure that on early 3.4
versions
there is a full-featured support of that command) that allows to have same neat and handy
command to build a multi-arch image as with Docker:
$ podman buildx build --push \
--tag your-username/multiarch-example:latest \
--platform linux/amd64,linux/arm/v7,linux/arm64 .
According to the official documentation:
podman buildx build
command is an alias ofpodman build
. Not allbuildx build
features are available in Podman. Thebuildx build
option is provided for scripting compatibility.
So, we may use it on our own risk.
By the time of writing this post, Podman 4.3.1 on Fedora 37 was able to execute buildx build
command the same way you would have it with Docker buildx
and provide valid
multi-architecture images.
What we have
For me it seems that Podman becomes a good alternative to Docker with every release. In the most recent versions it has everything I need as well as some nice features like systemd service/Kubernetes POD Yaml generators. Maybe write something about that feature.
If you have any questions, feel free to reach me out via my contacts available or via email.