In Chapter 2, you created GitHub Actions workflows that respond to code changes. Now we connect the build stage to actual container images. When you push code, GitHub Actions should automatically build your Docker image, tag it appropriately, and push it to a registry (Docker Hub, GitHub Container Registry, or similar).
The challenge: Your local machine might be Intel (amd64) while your cloud servers run ARM (arm64). A Docker image built on your Mac M1 may not run on your cloud Kubernetes cluster. This chapter teaches you how to build once and push images that work everywhere—using Docker buildx for multi-platform builds and GitHub Actions caching for speed.
You've built Docker images locally:
Output:
This workflow is manual, local, and single-platform. Every developer builds on their own machine. If you're on Apple Silicon and your teammate is on Intel, the images might have subtle differences. And there's no audit trail.
In CI, this becomes automated and declarative:
The image now has a clear source (Git commit), supports all architectures, and is reproducible.
Let's make this concrete with three scenarios:
Scenario 1: You develop on Mac M1, your team uses Intel
Local build on your Mac:
Output:
When your teammate pulls and runs it on their Intel machine, Docker uses QEMU emulation, and everything runs at 1/10 speed. Not acceptable for production.
Scenario 2: CI builds only for amd64, but Kubernetes schedules on ARM nodes
A common setup: GitHub Actions runner is amd64, your cluster has some ARM nodes. You build an amd64 image, push it, Kubernetes tries to schedule it on ARM → ImagePullBackOff error. The cluster can't run the image.
Scenario 3: You want to run your agent on both cloud servers and embedded devices
You build once with both platforms:
Output:
Now one tag (agent:latest) points to a multi-platform image. Kubernetes on any architecture pulls the right variant automatically.
Docker buildx is an extended build capability that uses QEMU (a machine emulator) to build for architectures different from your host.
Regular docker build creates an image for your current architecture only:
Output:
Docker buildx creates images for specified architectures:
Output:
GitHub Actions runners already have buildx installed. You need to initialize the builder and enable QEMU emulation:
Output (when workflow runs):
What each step does:
Building locally does nothing without pushing to a registry. You need to authenticate with your container registry (Docker Hub, GHCR, Quay, etc.).
GHCR is GitHub's registry, and GitHub Actions has built-in support. Here's how:
Step 1: Create a GitHub Token
Your workflow needs permission to push to GHCR. GitHub Actions provides an automatic token for this:
Output (when this step runs):
The ${{ secrets.GITHUB_TOKEN }} is a built-in secret that GitHub automatically creates—no setup needed.
Step 2: Use Authenticated Registry in Build
Once authenticated, push to GHCR with your image name:
Output (when workflow runs):
If using Docker Hub instead of GHCR:
Step 1: Store credentials as GitHub Secrets
In your repository settings (Settings → Secrets and Variables → Actions), add:
Step 2: Authenticate in workflow
Output (when this step runs):
A single image needs multiple identities:
Output (example with commit abc1234567...):
Now you have:
You might want latest to only update when pushing to main:
Output (push to main branch):
Output (push to feature branch):
Building a Docker image from scratch takes time (dependency downloads, compilation, etc.). GitHub Actions caching reuses layers between builds.
Docker caches layers locally—if a layer's inputs haven't changed, it reuses the cached layer:
If you change only application code (line: COPY . .), Docker reuses the pip layer because requirements.txt is unchanged. This saves ~30 seconds per build.
For even faster multi-platform builds, export cache to a registry:
Output (first build):
Output (second build):
What's happening:
This is a GitHub-hosted cache that persists between workflow runs.
Let's put it all together. Here's a production-ready workflow for your Module 6 FastAPI agent:
Output (when you push to main):
What each section does:
Push this workflow to your repository and make a commit to main:
Output (in GitHub Actions UI):
Then verify the image exists in your registry:
Output:
Your Module 6 FastAPI agent is no longer a local Docker file—it's a reproducible, versioned artifact in a registry. When you're ready to deploy to Kubernetes (Chapters 5-9), you pull this image with full confidence:
Ask Claude: "Optimize my GitHub Actions Docker build workflow. I'm currently building for linux/amd64 only, builds take 5 minutes, and I want to support ARM for Mac users."
Before accepting the output, validate:
Then iterate with: "Show me how to tag images with both the commit SHA and 'latest' only on the main branch, using the metadata action."
Look for:
Finally ask: "My builds are timing out at 20 minutes on ARM emulation. How do I split the build into separate jobs per platform instead of emulating?"
This explores the tradeoff between single builds (simpler, uses emulation) and split builds (faster per-platform, requires coordination).
You built a gitops-deployment skill in Chapter 0. Test and improve it based on what you learned.
Ask yourself:
If you found gaps: