How to Run Multi-Arch Docker Images on AWS Graviton?
Containerized workloads can run seamlessly on Amazon EC2 Graviton nodes by building and pushing multi-architecture Docker images that package both x86_64 and ARM64 binaries behind a single image manifest list. When an orchestration platform like Amazon EKS or Amazon ECS pulls the container image onto a Graviton instance, the container runtime automatically inspects the manifest list, detects the underlying ARM64 architecture of the host, and downloads the corresponding layers without requiring changes to deployment configurations or image tags.
Understanding Amazon EC2 Graviton and ARM64
Amazon EC2 Graviton processors are custom-built by AWS using 64-bit
Arm Neoverse cores (linux/arm64 or aarch64).
Compared to traditional x86_64 (linux/amd64) instances,
Graviton nodes deliver significant price-performance improvements and
lower energy consumption for cloud-native workloads.
When migrating or running hybrid clusters that mix x86_64 and
Graviton instances, managing separate image tags (such as
my-app:v1-amd64 and my-app:v1-arm64)
introduces operational overhead. Multi-architecture container images
eliminate this friction by uniting architecture-specific image digests
under a single Open Container Initiative (OCI) image index or Docker
manifest list.
The cost-performance efficiency gain when shifting a workload from x86_64 to Graviton can be expressed as:
\[\text{Efficiency Gain} = \left(1 - \frac{C_{\text{Graviton}} \times T_{\text{Graviton}}}{C_{\text{x86}} \times T_{\text{x86}}}\right) \times 100\%\]
where \(C\) represents the hourly instance cost and \(T\) represents the average request execution time on each architecture.
How Multi-Architecture Manifest Lists Work
A multi-architecture image is not a single binary file containing instructions for multiple CPUs. Instead, it is a hierarchy of manifests stored in a container registry such as Amazon Elastic Container Registry (ECR):
- Image Index (Manifest List): The top-level metadata
object associated with a tag (for example,
123456789012.dkr.ecr.us-east-1.amazonaws.com/my-app:latest). It contains pointers to multiple platform-specific image manifests alongside their OS and CPU architecture metadata. - Platform Manifests: Separate manifest files for
linux/amd64andlinux/arm64, each referencing its own specific configuration and filesystem layers. - Runtime Resolution: When
containerdor the Docker daemon on a Graviton node requestsmy-app:latest, it downloads the manifest list first, matches its locallinux/arm64platform against the entries, and pulls only thelinux/arm64layers.
Building Multi-Architecture Images with Docker Buildx
Docker Buildx is a CLI plugin that extends the
docker build command using the BuildKit engine. It allows
you to build images for multiple platforms simultaneously using QEMU
emulation, cross-compilation, or native multi-node builders.
1. Select a Multi-Architecture Base Image
Ensure your Dockerfile starts with an official base
image that already supports both linux/amd64 and
linux/arm64, such as alpine,
debian, ubuntu, node,
python, or golang. You can verify base image
support using the docker buildx imagetools inspect
command:
docker buildx imagetools inspect alpine:3.202. Write a Platform-Aware Dockerfile
BuildKit automatically injects platform arguments like
TARGETOS and TARGETARCH when running
multi-platform builds. For compiled languages like Go or Rust, using
cross-compilation on the host builder is much faster than emulating
instructions via QEMU:
FROM --platform=$BUILDPLATFORM golang:1.22-alpine AS builder
ARG TARGETOS
ARG TARGETARCH
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -o /out/app ./cmd/server
FROM alpine:3.20
COPY --from=builder /out/app /usr/local/bin/app
ENTRYPOINT ["/usr/local/bin/app"]3. Configure the Buildx Builder and Push to Amazon ECR
Create a new builder instance backed by the
docker-container driver, authenticate with Amazon ECR, and
build and push both architectures in one step:
# Authenticate Docker to Amazon ECR
aws ecr get-login-password --region us-east-1 | \
docker login --username AWS --password-stdin 123456789012.dkr.ecr.us-east-1.amazonaws.com
# Create and bootstrap a multi-arch builder instance
docker buildx create --name multiarch-builder --driver docker-container --use
docker buildx inspect --bootstrap
# Build and push the multi-architecture manifest and images directly to ECR
docker buildx build \
--platform linux/amd64,linux/arm64 \
--tag 123456789012.dkr.ecr.us-east-1.amazonaws.com/my-app:1.0.0 \
--push .Native Builds Without Emulation Overhead
While QEMU emulation is convenient for small builds, compiling heavy C/C++ dependencies or large container layers under emulation can be slow. In production CI/CD pipelines (such as AWS CodeBuild or GitHub Actions), the recommended pattern is to build each architecture natively on separate compute runners:
- Run an x86_64 build job on a standard runner and push
my-app:1.0.0-amd64. - Run an ARM64 build job on a Graviton runner
(
ARM_CONTAINERin AWS CodeBuild) and pushmy-app:1.0.0-arm64. - Combine both digests into a single multi-architecture manifest list:
docker buildx imagetools create \
--tag 123456789012.dkr.ecr.us-east-1.amazonaws.com/my-app:1.0.0 \
123456789012.dkr.ecr.us-east-1.amazonaws.com/my-app:1.0.0-amd64 \
123456789012.dkr.ecr.us-east-1.amazonaws.com/my-app:1.0.0-arm64Deploying to Amazon EKS and Amazon ECS on Graviton
Once your multi-architecture image is published in Amazon ECR, workloads can be scheduled across Graviton and x86_64 nodes using a single deployment specification.
Amazon EKS (Kubernetes)
In Amazon EKS, Kubernetes automatically populates the
kubernetes.io/arch node label (amd64 or
arm64). With a multi-architecture image, a standard
Deployment without architecture restrictions can be scheduled on either
node type by Cluster Autoscaler or Karpenter:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 4
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/arch
operator: In
values:
- amd64
- arm64
containers:
- name: my-app
image: 123456789012.dkr.ecr.us-east-1.amazonaws.com/my-app:1.0.0
ports:
- containerPort: 8080If you use Karpenter for node provisioning, including both
amd64 and arm64 in your NodePool
requirements allows Karpenter to dynamically select the lowest-cost Spot
or On-Demand Graviton instances while seamlessly falling back to x86_64
capacity if needed.
Amazon ECS
In Amazon ECS, EC2 capacity providers using Graviton instances
(m7g, c7g, r7g) pull the
linux/arm64 variant automatically from the
multi-architecture ECR tag. If you also run tasks on AWS Fargate, you
can switch a task definition to Graviton by setting the
runtimePlatform block without changing the
image URI:
{
"family": "my-app-task",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"cpu": "1024",
"memory": "2048",
"runtimePlatform": {
"cpuArchitecture": "ARM64",
"operatingSystemFamily": "LINUX"
},
"containerDefinitions": [
{
"name": "my-app",
"image": "123456789012.dkr.ecr.us-east-1.amazonaws.com/my-app:1.0.0",
"essential": true
}
]
}Best Practices for Graviton Multi-Arch Workloads
- Pin Multi-Arch Base Images by Digest of the Index:
When pinning image digests for security, always reference the top-level
manifest list digest rather than a single architecture's manifest digest
so both
amd64andarm64nodes can resolve the image. - Audit Native Dependencies: Interpreted languages
like Python, Node.js, and Ruby are largely architecture-independent, but
packages that compile native C extensions (such as
uvloop,psycopg2, orsharp) must have prebuiltaarch64wheels/binaries or build tools installed during thearm64container build stage. - Verify Runtime Architecture: Add automated
integration tests on Graviton runners to confirm that
uname -mreturnsaarch64inside the running container and that performance and memory profiles meet expectations under production load.