Free lesson · GenAI Platform Engineering

Build multi-architecture images for GKE

You will build Docker images that support both amd64 and arm64 architectures for GKE Autopilot flexibility. Use docker buildx with --platform linux/amd64,linux/arm64 to create multi-arch manifests. Configure the CI pipeline to build both architectures using QEMU emulation on GitHub Actions runners. Push multi-arch images to Artifact Registry and verify that GKE pulls the correct architecture automatically. Benchmark: compare build times for single-arch vs multi-arch builds. Add a nodeSelector in K8s manifests to target specific architectures when needed for GPU workloads.

Course: DevOps Foundations for GenAI Engineers · Chapter 3 · Container Image CI/CD

Free to read — no subscription required.

Introduction

When you push a Python AI service image to Artifact Registry, the kubelet on a GKE node will only pull it if the image manifest declares support for that node's architecture. Teams that ship a single linux/amd64 image discover this the hard way the first time an ARM64 node spins up in an Autopilot cluster: the pod fails with exec format error on startup, the deployment never reaches Ready, and the on-call engineer chases a phantom application crash before noticing the node-arch mismatch. The fix is to ship one image tag that carries both AMD64 and ARM64 variants under a single manifest index, so the kubelet picks the right binary regardless of where the scheduler lands the pod.

By the end of this lesson you'll be able to configure Docker Buildx to produce a multi-architecture image, push it as a single tag to Artifact Registry, and constrain GKE scheduling so GPU workloads only land on AMD64 nodes.

Key Terminology

  • Multi-architecture manifest index — a single registry entry under one image tag that points at architecture-specific image layers (e.g. linux/amd64 and linux/arm64); lets the kubelet pull the right variant transparently for any node.
  • Docker Buildx — the Docker CLI extension that builds images for multiple platforms in one invocation, using QEMU to emulate non-native architectures on the build host.
  • QEMU user-space emulation — the mechanism that lets an AMD64 build host execute ARM64 instructions during a build step; correct but roughly 3-4× slower than native execution, which dominates ARM64 build wall-clock time.
  • kubernetes.io/arch node label — the well-known label GKE attaches to every node identifying its CPU architecture (amd64 or arm64); the value nodeSelector matches on to constrain pod scheduling.
  • Manifest index inspection — running docker buildx imagetools inspect <tag> to confirm a pushed multi-arch image actually lists both architecture entries before depending on it in a deployment.

Concepts

Why GKE needs both architectures under one tag

GKE Autopilot schedules workloads across both AMD64 and ARM64 node pools, preferring whichever provides the best cost-performance ratio at scheduling time. ARM64 nodes typically cost 20-30% less for CPU-bound inference services. If your image only ships linux/amd64, the scheduler is forced onto AMD64 even when ARM64 capacity is cheaper and available. A multi-architecture image removes that constraint by publishing one tag whose manifest index references both variants, and the kubelet selects the matching layer set automatically.

The Buildx + QEMU build pipeline

Docker Buildx extends docker build with multi-platform support. Given --platforms linux/amd64,linux/arm64, it runs the Dockerfile once per architecture — using QEMU to emulate the non-native target on the build host — then assembles a single manifest index that references both layer sets and pushes the whole bundle under one tag (see Code Walkthrough). The cost is real: emulated ARM64 builds run roughly 3-4× slower than native AMD64, so caching per-architecture dependency layers is the key optimization to keep code-only rebuilds under two minutes.

Loading diagram...

Constraining scheduling for GPU workloads

GPU workloads still pin to AMD64 because NVIDIA CUDA targets AMD64 only. Even when the image is multi-arch, a GPU pod that lands on an ARM64 node fails to attach the GPU and crashes. The fix is a nodeSelector on kubernetes.io/arch: amd64 plus the accelerator label — the multi-arch image becomes a hedge for the non-GPU paths, while GPU paths stay constrained explicitly (see Code Walkthrough).

Code Walkthrough

Now that you've seen why a single manifest index matters and how Buildx assembles it, the snippets below put both ideas into practice — first building and pushing a multi-arch image from GitHub Actions, then constraining a GPU deployment to AMD64 nodes so it cannot land on an ARM64 node by accident.

Code snippetyaml
1 - name: Set up QEMU 2 uses: docker/setup-qemu-action@v3 3 4 - name: Set up Docker Buildx 5 uses: docker/setup-buildx-action@v3 6 7 - name: Build and push multi-arch image 8 uses: docker/build-push-action@v5 9 with: 10 context: . 11 platforms: linux/amd64,linux/arm64 12 push: true 13 tags: ${{ steps.meta.outputs.tags }} 14 cache-from: type=gha 15 cache-to: type=gha,mode=max
  • QEMU setup installs the user-space emulators on the AMD64 GitHub Actions runner so RUN instructions that execute ARM64 binaries (e.g. compiling a C extension during pip install) succeed.
  • Buildx setup swaps the default builder for the multi-platform one.
  • platforms: linux/amd64,linux/arm64 runs the Dockerfile once per target and pushes a single manifest index under each tag in steps.meta.outputs.tags.
  • cache-to: type=gha,mode=max persists every intermediate layer per architecture, so a subsequent build whose only delta is application code reuses the expensive emulated dependency layers.
Code snippetyaml
1spec: 2 nodeSelector: 3 kubernetes.io/arch: amd64 4 cloud.google.com/gke-accelerator: nvidia-tesla-t4 5 containers: 6 - name: inference 7 image: us-central1-docker.pkg.dev/my-project/ai-services/inference:v1.2.3 8 resources: 9 limits: 10 nvidia.com/gpu: 1
  • kubernetes.io/arch: amd64 keeps the GPU pod off ARM64 nodes even though the image is multi-arch — the variant the kubelet pulls is whichever matches the node, and AMD64 is the only one with working CUDA.
  • cloud.google.com/gke-accelerator: nvidia-tesla-t4 narrows further to nodes carrying T4 GPUs, so the scheduler does not place the pod on a GPU-less AMD64 node.

You'll know it works when docker buildx imagetools inspect <tag> lists both linux/amd64 and linux/arm64 entries with non-zero size, and a pod scheduled to each architecture prints the matching platform.machine() value from inside the container.

Do's and Don'ts

Having just walked through the build pipeline and the AMD64-pinned GPU deployment, the items below capture the handful of habits that keep multi-arch images working in production — and the shortcuts that quietly break them.

Do's

  1. Do cache dependency layers per architecture — order the Dockerfile so requirements.txt is copied and installed before application code, and use cache-to: type=gha,mode=max so the emulated ARM64 pip install only re-runs when requirements change.
  2. Do verify the manifest index after push — run docker buildx imagetools inspect <tag> and confirm both linux/amd64 and linux/arm64 entries are present with non-zero size before wiring the tag into any deployment.
  3. Do pin GPU workloads to kubernetes.io/arch: amd64 — even with a multi-arch image, NVIDIA CUDA only targets AMD64, so a GPU pod on an ARM64 node will fail to attach the device and crash on startup.

Don'ts

  1. Don't ship a single-architecture image and assume runtime emulation will rescue it — container runtimes on GKE nodes do not transparently emulate cross-architecture binaries; the pod will fail with exec format error on container start.
  2. Don't trust a green push without inspecting the manifest — a successful push does not guarantee both variants are listed; a malformed manifest can leave the kubelet falling back to the first entry on every node.
  3. Don't skip per-architecture caching — without it, every push re-runs the 12-minute QEMU-emulated ARM64 pip install and inflates queue times across the team.

This lesson is free to read. Its 3 hands-on labs — real code, in a cloud IDE — are part of the GenAI Platform Engineering subscription.

From · cancel anytime

More free lessons in DevOps Foundations for GenAI Engineers

All free lessons in GenAI Platform Engineering