Building and Using Multi‑Architecture Docker Images with Buildx, CI/CD Integration, and Promotion
This article explains what multi‑architecture Docker images are, why they are useful, and provides step‑by‑step instructions for building them with traditional Docker commands or Docker buildx, integrating the builds into Jenkins and GitHub Actions pipelines, promoting images across environments, scanning for vulnerabilities, and important considerations to keep in mind.
What Is a Multi‑Architecture Container Image?
A multi‑architecture Docker image is a manifest list that references binaries and libraries compiled for several CPU architectures (e.g., ARM, x86, RISC‑V). It allows the same application to run on different hardware without maintaining separate images for each architecture.
Multi‑Architecture Container Use Cases
Performance and Cost Optimization : Build and deploy architecture‑specific images to achieve better performance and lower resource consumption.
Cross‑Platform Development : Use docker buildx to create images that run on both ARM and x86 for testing and release.
IoT/Edge Devices : Deploy images that run on ARM‑based edge devices as well as x86 servers.
Benefits of Using Multi‑Architecture Container Images
Run Docker images on multiple CPU architectures.
Select environmentally friendly CPU architectures.
Seamlessly migrate from one architecture to another.
Leverage arm64 for better performance and cost savings.
Utilize more CPU cores on arm64 platforms.
How to Build Multi‑Architecture Container Images?
There are several approaches; the article focuses on two widely used methods.
Traditional Docker build commands.
Docker buildx for multi‑arch builds.
Using Traditional Docker Build Commands
Manually build separate images for each architecture on machines that match the target CPU, push them to a registry, and create a manifest JSON that references both images.
FROM nginx
RUN echo "Hello multiarch" > /usr/share/nginx/html/index.html # on amd64 machine
docker build -t username/custom-nginx:v1-amd64 .
docker push username/custom-nginx:v1-amd64
# on arm64 machine
docker build -t username/custom-nginx:v1-arm64 .
docker push username/custom-nginx:v1-arm64
# create manifest index file
docker manifest create \
username/custom-nginx:v1 \
username/custom-nginx:v1-amd64 \
username/custom-nginx:v1-arm64
# push manifest
docker manifest push username/custom-nginx:v1Using Docker Buildx
With buildx a single command can build and push images for multiple platforms.
docker buildx build \
--push \
--platform linux/arm64,linux/amd64 \
-t username/custom-nginx:v1 .Behind the scenes, docker buildx uses BuildKit and a container running moby/buildkitd with QEMU binaries to emulate the target CPU instruction sets. The appropriate QEMU binary (e.g., /usr/bin/buildkit-qemu-aarch64 ) is selected based on the --platform flag.
After building both images, the --push flag creates a manifest and pushes the images together to the registry.
Inspecting Multi‑Architecture Manifests
You can view the manifest with docker manifest inspect -v or with docker buildx imagetools inspect for a more readable format.
$ docker manifest inspect -v nginx
[ ... JSON output showing Ref and platform fields ... ]Integrating Multi‑Architecture Builds into CI/CD
Embedding the build process into CI pipelines ensures consistent image creation across environments.
Jenkins Multi‑Architecture CI
Jenkins Docker plugin does not support multi‑arch directly, so buildx is used.
pipeline {
agent { label 'worker1' }
options { timestamps(); timeout(time: 30, unit: 'MINUTES'); buildDiscarder(logRotator(numToKeepStr: '10')) }
environment {
DOCKER_REGISTRY_PATH = "https://registry.example.com"
DOCKER_TAG = "v1"
}
stages {
stage('build-and-push') {
steps {
script {
docker.withRegistry(DOCKER_REGISTRY_PATH, ecrcred_dev) {
sh '''
export DOCKER_BUILDKIT=1
if [[ $(docker buildx inspect --bootstrap | head -n 2 | grep Name | awk -F" " '{print $NF}') != "multiarch" ]]; then
docker buildx rm multiarch || true
docker buildx create --name multiarch --use
docker buildx inspect --bootstrap
fi
docker buildx build --push --platform linux/arm64,linux/amd64 \
-t "$DOCKER_REGISTRY_PATH"/username/custom-nginx:"$DOCKER_TAG" .
'''
}
}
}
}
}
}GitHub Actions CI for Multi‑Architecture Images
GitHub Actions also supports multi‑arch builds using QEMU and Buildx.
name: docker-multi-arch-push
on:
push:
branches: ['main']
jobs:
docker-build-push:
runs-on: ubuntu-20.04
steps:
- name: Checkout Code
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push
uses: docker/build-push-action@v3
with:
context: .
file: ./Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: username/custom-nginx:latestPromoting Multi‑Architecture Images to Higher Environments
Because docker pull only fetches the image matching the host CPU, you must pull each architecture explicitly and then create a manifest for promotion. Tools like skopeo or crane can copy all architectures in a single command.
$ skopeo login --username $USER docker.io
$ skopeo copy -a docker://dev-account/custom-nginx:v1 docker://prod-account/custom-nginx:v1If you prefer plain Docker commands, you can pull, tag, and push each architecture, then create and push a manifest:
# Pull DEV images
docker pull --platform=amd64 $DOCKER_IMAGE_NAME_DEV:$DOCKER_TAG
docker pull --platform=arm64 $DOCKER_IMAGE_NAME_DEV:$DOCKER_TAG
# Tag for STAGE
docker tag $DOCKER_IMAGE_NAME_DEV:$DOCKER_TAG $DOCKER_IMAGE_NAME_STAGE:$DOCKER_TAG-amd64
docker tag $DOCKER_IMAGE_NAME_DEV:$DOCKER_TAG $DOCKER_IMAGE_NAME_STAGE:$DOCKER_TAG-arm64
# Push each architecture
docker push $DOCKER_IMAGE_NAME_STAGE:$DOCKER_TAG-amd64
docker push $DOCKER_IMAGE_NAME_STAGE:$DOCKER_TAG-arm64
# Create and push manifest
docker manifest create \
$DOCKER_IMAGE_NAME_STAGE:$DOCKER_TAG \
--amend $DOCKER_IMAGE_NAME_STAGE:$DOCKER_TAG-amd64 \
--amend $DOCKER_IMAGE_NAME_STAGE:$DOCKER_TAG-arm64
docker manifest push $DOCKER_IMAGE_NAME_STAGE:$DOCKER_TAGScanning Multi‑Architecture Images for Vulnerabilities
Tools such as Trivy, Gryp, or Docker Scan can be used, but each architecture must be pulled and scanned separately because the default docker pull only retrieves the image matching the host CPU.
# Scan amd64 image
docker pull --platform=amd64 nginx:latest
trivy image nginx:latest
# Scan arm64 image
docker pull --platform=arm64 nginx:latest
trivy image nginx:latestImportant Considerations When Using Multi‑Architecture Containers
Storing images for additional architectures consumes extra storage.
Building multi‑arch images takes more time; QEMU emulation for arm64 is especially resource‑intensive.
Emulated binaries run slower than native binaries.
Buildx may encounter issues such as missing base images for arm64 or the need for sudo privileges for certain cross‑compilation steps.
All images must be scanned individually.
Buildx multi‑arch builds are currently only supported on amd64 hosts.
Conclusion
This article introduced multi‑architecture container images, their use cases, and how to build them using traditional Docker commands or Docker buildx. It demonstrated integration with Jenkins and GitHub Actions pipelines, methods for promoting images across environments, scanning for vulnerabilities, and highlighted key considerations for successful adoption.
By leveraging multi‑arch images, developers can build once and run everywhere, seamlessly migrate between CPU architectures, and achieve better performance and cost efficiency by deploying architecture‑optimized images.
DevOps Cloud Academy
Exploring industry DevOps practices and technical expertise.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.