Day 10: Building & Running Multi-Arch Docker Containers

Table of contents
- Learning points:
- ✅ Challenge 1: Build a multi-arch image supporting linux/amd64, linux/arm64, and linux/arm/v7
- ✅ Challenge 2: Push the multi-arch image to Docker Hub & Verify the Docker Manifest of your pushed image
- ✅ Challenge 3: Deploy your multi-arch image on AWS Graviton (ARM64) & a regular EC2 x86_64 server
- ✅ Challenge 4: Use Docker Squash to minimize the image size while keeping multi-arch support
- ✅ Challenge 5: Build a multi-arch Alpine-based image with a minimal footprint
- ✅ Challenge 6: Deploy a Raspberry Pi-specific multi-arch image and verify execution

Learning points:
🔹 What are Multi-Arch Docker Containers? – Why they matter and how they support different platforms (x86_64, ARM64, ARMv7).
🔹 Docker Buildx – Enabling multi-platform builds usingdocker buildx
.
🔹 QEMU for CPU Emulation – Running containers built for different architectures on a single machine.
🔹 Docker Manifest – How Docker Hub serves platform-specific images.
🔹 Deploying Multi-Arch Images – Running them on AWS, Raspberry Pi, or edge devices.LEARN:
📚️ Resources:
📌 Building Multi-Arch Images – Docker Docs
📌 Docker Buildx & QEMU – Docker Blog
📌 Understanding Docker Manifest – Docker Reference
📌 Deploying Multi-Arch Containers – AWS Graviton Guide
Initial Tasks:
✅ Task 1: Verify your system’s architecture:
uname -m
✅ Task 2: Enable Docker Buildx for multi-arch builds:
docker buildx create --use docker buildx inspect --bootstrap
✅ Task 3: Install QEMU for CPU architecture emulation:
docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
✅ Task 4: Test running an ARM-based image on an x86_64 system:
docker run --rm --platform linux/arm64 busybox uname -m
In the modern cloud-native world, understanding multi-architecture Docker images is crucial for building truly portable containerized applications. This guide will walk you through 6 practical challenges designed to strengthen your skills with multi-arch Docker images.
Each challenge is presented with:
🎯 Objective: What we're trying to achieve
🛠️ Solution: Step-by-step commands with explanations
💡 Key Takeaway: Important concepts to remember
🔍 Verification: How to confirm it worked correctly
Let's dive in!
✅ Challenge 1: Build a multi-arch image supporting linux/amd64
, linux/arm64
, and linux/arm/v7
🎯 Objective: Create a Docker image that can run on multiple CPU architectures including x86_64 (amd64), 64-bit ARM (arm64), and 32-bit ARM v7.
🛠️ Solution:
# Step 1: Create a simple Dockerfile
cat > Dockerfile << 'EOF'
FROM alpine:latest
RUN apk add --no-cache curl jq
WORKDIR /app
COPY app.sh .
RUN chmod +x app.sh
CMD ["./app.sh"]
EOF
# Step 2: Create a simple app script
cat > app.sh << 'EOF'
#!/bin/sh
echo "Hello from $(uname -m) architecture!"
while true; do
sleep 10
done
EOF
# Step 3: Set up Docker BuildX (if not already configured)
docker buildx create --name mybuilder --use
# Step 4: Build the multi-architecture image
docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 \
--tag yourusername/multi-arch-demo:latest \
--push .
🔍 Verification: Check that the builder is using the appropriate platforms:
docker buildx inspect
💡 Key Takeaway:
Docker BuildX is essential for creating multi-architecture images
The
--platform
flag specifies which architectures to supportUsing the
--push
flag makes BuildX automatically push to Docker HubWithout specifying
--load
or--push
, BuildX won't save the image locally
✅ Challenge 2: Push the multi-arch image to Docker Hub & Verify the Docker Manifest of your pushed image
🎯 Objective: Upload your multi-arch image to Docker Hub and examine its manifest to confirm it supports all target architectures.
🛠️ Solution:
# Step 1: Log in to Docker Hub (if not already logged in)
docker login
# Step 2: If you didn't use --push in the previous challenge:
docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 \
--tag yourusername/multi-arch-demo:latest \
--push .
# Step 3: Inspect the manifest
docker manifest inspect yourusername/multi-arch-demo:latest
🔍 Verification: The manifest output should show entries for all three platforms:
# Sample output (truncated)
[
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"digest": "sha256:...",
"platform": {
"architecture": "amd64",
"os": "linux"
}
},
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"digest": "sha256:...",
"platform": {
"architecture": "arm64",
"os": "linux"
}
},
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"digest": "sha256:...",
"platform": {
"architecture": "arm",
"os": "linux",
"variant": "v7"
}
}
]
💡 Key Takeaway:
A multi-arch image is actually a manifest list pointing to architecture-specific images
The Docker manifest command reveals the internals of multi-arch images
Docker automatically serves the correct architecture image based on the client's architecture
✅ Challenge 3: Deploy your multi-arch image on AWS Graviton (ARM64) & a regular EC2 x86_64 server
🎯 Objective: Demonstrate the portability of multi-arch images by deploying the same image to both x86 and ARM-based cloud instances.
🛠️ Solution:
# Step 1: Launch an ARM64 AWS Graviton instance
# (Using AWS CLI as an example)
aws ec2 run-instances \
--image-id ami-0eb5f3f64b10d3e0e \ # Amazon Linux 2 ARM64 AMI
--instance-type t4g.micro \ # Graviton-based instance
--count 1 \
--key-name your-key-pair \
--security-groups your-security-group
# Step 2: Launch a regular x86_64 EC2 instance
aws ec2 run-instances \
--image-id ami-0c55b159cbfafe1f0 \ # Amazon Linux 2 x86_64 AMI
--instance-type t2.micro \ # x86-based instance
--count 1 \
--key-name your-key-pair \
--security-groups your-security-group
# Step 3: Connect to each instance and run the container
# For ARM64 instance:
ssh -i your-key.pem ec2-user@arm64-instance-ip
sudo yum install -y docker
sudo systemctl start docker
sudo docker run yourusername/multi-arch-demo:latest
# For x86_64 instance:
ssh -i your-key.pem ec2-user@x86-instance-ip
sudo yum install -y docker
sudo systemctl start docker
sudo docker run yourusername/multi-arch-demo:latest
🔍 Verification: On both instances, you should see different architecture information in the output:
On ARM64: "Hello from aarch64 architecture!"
On x86_64: "Hello from x86_64 architecture!"
💡 Key Takeaway:
Multi-arch images enable true "write once, run anywhere" container deployments
AWS Graviton instances provide cost-effective ARM64 computing in the cloud
Docker automatically selects the correct image variant for each architecture
✅ Challenge 4: Use Docker Squash to minimize the image size while keeping multi-arch support
🎯 Objective: Reduce the size of your multi-arch Docker images by flattening layers while maintaining architecture support.
🛠️ Solution:
# Step 1: Create a multi-stage Dockerfile that produces more layers
cat > Dockerfile.multi << 'EOF'
FROM alpine:latest AS builder
RUN apk add --no-cache build-base
WORKDIR /build
COPY app.c .
RUN gcc -static -o app app.c
FROM alpine:latest
RUN apk add --no-cache curl
RUN apk add --no-cache jq
RUN mkdir -p /app/logs
COPY --from=builder /build/app /app/
WORKDIR /app
CMD ["./app"]
EOF
# Step 2: Create a simple C app
cat > app.c << 'EOF'
#include <stdio.h>
int main() {
printf("Hello from squashed multi-arch image!\n");
return 0;
}
EOF
# Step 3: Enable experimental features for squash support
export DOCKER_CLI_EXPERIMENTAL=enabled
# Step 4: Build and squash the multi-arch image
docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 \
--tag yourusername/squashed-multi-arch:latest \
--squash \
--push \
-f Dockerfile.multi .
🔍 Verification: Compare the size of the squashed vs. non-squashed images:
# Pull both images on a local machine
docker pull yourusername/multi-arch-demo:latest
docker pull yourusername/squashed-multi-arch:latest
# Compare sizes
docker images | grep yourusername
💡 Key Takeaway:
The
--squash
flag combines multiple layers into one, reducing image sizeSquashing works with multi-arch builds when using BuildX
Squashed images remain portable across architectures
Layer squashing can significantly reduce image size, especially when there are many
RUN
commands
✅ Challenge 5: Build a multi-arch Alpine-based image with a minimal footprint
🎯 Objective: Create an ultra-lightweight multi-architecture Docker image based on Alpine Linux.
🛠️ Solution:
# Step 1: Create a minimal Dockerfile
cat > Dockerfile.minimal << 'EOF'
FROM alpine:3.19 AS build
WORKDIR /app
COPY app.go .
RUN apk add --no-cache go && \
go build -ldflags="-s -w" -o app app.go
FROM scratch
COPY --from=build /app/app /
CMD ["/app"]
EOF
# Step 2: Create a simple Go application
cat > app.go << 'EOF'
package main
import (
"fmt"
"os"
"runtime"
)
func main() {
hostname, _ := os.Hostname()
fmt.Printf("Hello from %s running on %s/%s\n",
hostname, runtime.GOOS, runtime.GOARCH)
}
EOF
# Step 3: Build the minimal multi-arch image
docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 \
--tag yourusername/minimal-multi-arch:latest \
--file Dockerfile.minimal \
--push .
🔍 Verification: Pull and check the image size:
docker pull yourusername/minimal-multi-arch:latest
docker images | grep minimal-multi-arch
💡 Key Takeaway:
Using
scratch
as a base image eliminates all OS files, creating minimal imagesMulti-stage builds separate build tools from runtime requirements
Go binaries can be statically compiled to run in scratch containers
The
-ldflags="-s -w"
strips debug information to further reduce binary sizeMinimal images significantly reduce attack surface and speed up deployments
✅ Challenge 6: Deploy a Raspberry Pi-specific multi-arch image and verify execution
🎯 Objective: Create and deploy a multi-arch image optimized for Raspberry Pi hardware.
🛠️ Solution:
# Step 1: Create a Raspberry Pi-friendly Dockerfile
cat > Dockerfile.rpi << 'EOF'
FROM alpine:latest
RUN apk add --no-cache python3 py3-pip
RUN pip3 install --no-cache-dir RPi.GPIO
WORKDIR /app
COPY pi_app.py .
CMD ["python3", "pi_app.py"]
EOF
# Step 2: Create a simple Raspberry Pi app
cat > pi_app.py << 'EOF'
#!/usr/bin/env python3
import platform
import socket
import time
try:
import RPi.GPIO as GPIO
gpio_available = True
except (ImportError, RuntimeError):
gpio_available = False
def main():
hostname = socket.gethostname()
arch = platform.machine()
print(f"Running on {hostname} with {arch} architecture")
print(f"GPIO library available: {gpio_available}")
if gpio_available:
GPIO.setmode(GPIO.BCM)
GPIO.setup(18, GPIO.OUT)
print("Blinking LED on GPIO 18 (if connected)...")
while True:
GPIO.output(18, GPIO.HIGH)
time.sleep(1)
GPIO.output(18, GPIO.LOW)
time.sleep(1)
else:
print("GPIO library not available or not running on Raspberry Pi.")
while True:
print("Simulating LED blink...")
time.sleep(2)
if __name__ == "__main__":
main()
EOF
# Step 3: Build the Raspberry Pi-optimized multi-arch image
docker buildx build --platform linux/arm/v7,linux/arm64 \
--tag yourusername/rpi-multi-arch:latest \
--file Dockerfile.rpi \
--push .
# Step 4: On your Raspberry Pi:
# Connect via SSH or open a terminal on the Pi
ssh pi@raspberry-pi-ip
# Install Docker if not already installed
curl -sSL https://get.docker.com | sh
sudo usermod -aG docker pi
# Log out and log back in
# Pull and run the image
docker run --privileged yourusername/rpi-multi-arch:latest
🔍 Verification: You should see output indicating:
The Raspberry Pi architecture (either armv7l or aarch64 depending on your Pi model)
Whether GPIO is available (requires
--privileged
flag to access hardware)If an LED is connected to GPIO 18, it should start blinking
💡 Key Takeaway:
Hardware-specific libraries like RPi.GPIO require building architecture-specific images
Using
--privileged
gives containers access to hardware devicesMulti-arch images can include both 32-bit ARM (for older Pis) and 64-bit ARM (for Pi 3B+/4/5)
Building directly for specific hardware platforms improves compatibility and performance
Subscribe to my newsletter
Read articles from Hari Kiran B directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
