Today I learned about pushing multi-arch docker images. I discovered that it is not possible to push a docker image that share the same name but have different target architectures. I discovered this "feature" when trying to push a linux/arm64
and a linux/amd64
image to my private registry. As for HomeLabs it's pretty common to be running a Kubernetes cluster with 2 (or more) different CPU architectures. By having an image built for many platforms, it's able to be hosted on different nodes.
The article I used to learn about this feature!
The way to accomplish this is with manifest
command inside of docker. Although I wonder if I can use other tools for this as well.
How to create a manifest
Pull both the images you care about and re-tag them for your desired archtectures. In the following example I'll download the GitHub self hosted runner and download it for with the platform set to linux/arm64
.
# Input Variables
IMAGE_PATH="actions/gha-runner-scale-set-controller"
IMAGE_VERSION="0.9.3"
IMAGE_ARCH="linux/arm64
PRIVATE_REGISTRY="docker.example.com"
# Commands to run
docker pull ghcr.io/$IMAGE_PATH:$IMAGE_VERSION --platform $IMAGE_ARCH
docker tag ghcr.io/$IMAGE_PATH:$IMAGE_VERSION $PRIVATE_REGISTRY/$IMAGE_PATH/$IMAGE_ARCH:$IMAGE_VERSION
docker push $PRIVATE_REGISTRY/$IMAGE_PATH/$IMAGE_ARCH:$IMAGE_VERSION
docker rmi ghcr.io/$IMAGE_PATH:$IMAGE_VERSION
Because we are looking to use 2 images with the same name, we'll also want to re-run the above commands except with a change to the IMAGE_ARCH
. Set it to linux/amd64
and re-run. This will publish 2 new docker container our local registry.
After they have been uploaded, you'll want to create a manifest made up of both images and push that up. We can accomplish that with the 2 below commands.
docker manifest create $PRIVATE_REGISTRY/$IMAGE_PATH:$IMAGE_VERSION \
$PRIVATE_REGISTRY/$IMAGE_PATH/linux/arm64:$IMAGE_VERSION \
$PRIVATE_REGISTRY/$IMAGE_PATH/linux/amd64:$IMAGE_VERSION
docker manifest push $PRIVATE_REGISTRY/$IMAGE_PATH:$IMAGE_VERSION
With all of that completed, you'll have the manifest that includes both versions of images for one URL. I NEVER KNEW THIS BEFORE IT'S SO COOL!

linux/amd64
and linux/arm64
available for downloadWhat is a Manifest?
A manifest is a list of docker images that is possible to pull from the same URL. Their primary usage is to bundle multiple images that are of the same kind, but with different architectures supported. Although you can use a single image in a manifest it's recommended to use 2 or more.
Local script you can copy!
Sharing the love through a blog post! Thanks ChatGPT for the help :)
#!/bin/bash
set -x
# Input your local registry hostname here.
LOCAL_REGISTRY="docker.io"
if [[ $# -lt 4 ]]; then
echo "Usage: $0 <host> <image> <tag> <architectures>" >&2
echo " - remote registry host: The image remote host name (e.g., ghcr.io)"
echo " - image: The image name to pull (e.g., actions/actions-runner)."
echo " - tag: The image tag to pull (e.g., 2.319.1)."
echo " - architectures: A comma-separated list of architectures to pull (e.g., linux/amd64,linux/arm64)."
exit 1
fi
REMOTE_REGISTRY_HOST="$1"
IMAGE_PATH="$2"
IMAGE_VERSION="$3"
IMAGE_ARCHES="$4"
# Split architectures into an array
IFS=',' read -r -a arch_array <<< "$IMAGE_ARCHES"
manifest_images=""
# Loop through each architecture and pull the image
for arch in "${arch_array[@]}"; do
remote_image_path="$REMOTE_REGISTRY_HOST/$IMAGE_PATH:$IMAGE_VERSION"
private_image_path="$LOCAL_REGISTRY/$IMAGE_PATH/$arch:$IMAGE_VERSION"
echo "Pulling image '$remote_image_path'"
docker pull "$remote_image_path" --platform $arch
echo "Tagging image '$remote_image_path' to '$private_image_path'"
docker tag $remote_image_path $private_image_path
echo "Pushing '$private_image_path'"
docker push $private_image_path
echo "Removing '$remote_image_path' --platform $arch"
docker rmi $remote_image_path
echo "Removing '$private_image_path'"
docker rmi $private_image_path
manifest_images="$manifest_images $private_image_path"
done
echo "Creating manifest '$LOCAL_REGISTRY/$IMAGE_PATH:$IMAGE_VERSION $manifest_images'"
docker manifest create "$LOCAL_REGISTRY/$IMAGE_PATH:$IMAGE_VERSION" $manifest_images
docker manifest push "$LOCAL_REGISTRY/$IMAGE_PATH:$IMAGE_VERSION"