How can I build AMD64 and ARM64 Docker images in GitHub Actions?

Use Depot to build AMD64 and ARM64 images on native x86 and Arm machines in parallel, then push both variants under one multi-platform image tag. Local and CI builds can share the same persistent layer cache.

Last updated 2026-09-10

Use depot/build-push-action to build AMD64 and ARM64 Docker images in GitHub Actions. Depot sends the linux/amd64 build to a native x86 builder and the linux/arm64 build to a native Arm builder. Both builds run in parallel, without QEMU emulation, and are published under one image tag.

The workflow only needs to specify both platforms:

- uses: depot/build-push-action@v1
  with:
    project: <your-depot-project-id>
    context: .
    platforms: linux/amd64,linux/arm64
    push: true
    tags: ghcr.io/example/my-app:latest

Depot automatically gives each architecture a persistent Docker layer cache on fast NVMe storage. There's no QEMU setup step and no registry cache configuration to maintain.

A complete GitHub Actions workflow

This example authenticates to Depot with GitHub Actions OIDC, builds both architectures, and pushes the result to GitHub Container Registry:

name: Build multi-platform image

on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: depot-ubuntu-24.04
    permissions:
      contents: read
      id-token: write
      packages: write

    steps:
      - uses: actions/checkout@v4

      - uses: depot/setup-action@v1

      - name: Log in to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push AMD64 and ARM64 image
        uses: depot/build-push-action@v1
        with:
          project: <your-depot-project-id>
          context: .
          platforms: linux/amd64,linux/arm64
          push: true
          tags: |
            ghcr.io/${{ github.repository }}:latest
            ghcr.io/${{ github.repository }}:${{ github.sha }}

Before running the workflow, create a Depot project and add a GitHub OIDC trust relationship for the repository. The id-token: write permission lets GitHub issue a short-lived identity token to Depot, so the workflow doesn't need a long-lived Depot API token.

The example uses a GitHub Actions runner managed by Depot as well, but the container build is remote. You can run depot/build-push-action from a GitHub-hosted runner if you prefer. The AMD64 and ARM64 image builds still run on Depot's native builders.

Build the same image locally

You can run the same multi-platform build from your laptop with depot build:

depot build \
  --project <your-depot-project-id> \
  --platform linux/amd64,linux/arm64 \
  .

The build still runs on Depot's native x86 and Arm builders. It doesn't consume your laptop's CPU and memory or use QEMU on an x86 machine.

Use the same Depot project ID locally and in depot/build-push-action. The project's AMD64 and ARM64 layer caches are shared across every build. A developer can build an image locally, push the change, and let the GitHub Actions build reuse those layers. CI can warm the same cache before another developer runs depot build.

There's no cache archive to copy between the laptop and GitHub Actions. Depot attaches the project's persistent NVMe cache to each remote builder automatically. See local development with Depot for CLI setup and loading a built image into the local Docker daemon.

Why native builders matter

A typical GitHub Actions job runs on an AMD64 machine. Building an ARM64 image on that machine usually means installing QEMU and emulating Arm instructions during RUN steps in the Dockerfile.

That works, but emulation can turn compilation and dependency installation into the slowest part of the workflow. It also adds another moving part when architecture-specific commands behave differently under emulation.

The difference can be massive. Depot's PostHog multi-platform benchmark builds linux/amd64 and linux/arm64 images with both depot/build-push-action and docker/build-push-action on every commit. The Docker job uses QEMU for ARM64. Depot runs each build on its native architecture. Across the latest 10 commits, Depot is currently 32.9x faster.

Depot removes that layer. It starts one native builder for each requested architecture:

  1. The AMD64 image builds on an x86 machine.
  2. The ARM64 image builds on an Arm machine.
  3. Both builds execute at the same time.
  4. BuildKit combines the results into one multi-platform image.

Each builder has 16 CPUs and 32 GB of memory by default. Larger builders are available when the Dockerfile can use more parallelism.

One tag, two platform images

A multi-platform image isn't one filesystem that somehow runs on both processors. The registry stores an AMD64 image and an ARM64 image, then points one tag at a manifest list, also called an OCI image index.

When someone runs this command:

docker pull ghcr.io/example/my-app:latest

Docker reads the manifest list and pulls the variant that matches the machine. An x86 server receives the linux/amd64 image. An AWS Graviton instance or other Arm server receives the linux/arm64 image.

You can inspect the published platforms with:

docker buildx imagetools inspect ghcr.io/example/my-app:latest

The output should include manifests for both linux/amd64 and linux/arm64.

Push the multi-platform result

Use push: true when the goal is one image tag that supports both architectures. The registry is where the manifest list and both platform images live together.

Loading is different. A normal Docker daemon expects a local tag to resolve to an image for the current machine, and standard Buildx multi-platform loads commonly fail with:

docker exporter does not currently support exporting manifest lists

Depot has an enhanced --load implementation that can select and load the image matching the local machine. That's useful for integration tests, but it doesn't put both platform variants into a registry under one tag. Push the image when downstream AMD64 and ARM64 machines both need to pull it.

If the workflow only needs to test the image locally, build and load the runner's architecture instead:

- uses: depot/build-push-action@v1
  with:
    project: <your-depot-project-id>
    context: .
    platforms: linux/amd64
    load: true
    tags: my-app:test

Keep both architecture caches warm

Depot persists the Docker layer cache for the project automatically. Future AMD64 builds reuse AMD64 layers, and future ARM64 builds reuse ARM64 layers. The cache is available without downloading and uploading a cache archive at the start and end of every GitHub Actions job.

Dockerfile structure still decides how useful that cache is. Put stable dependency inputs before frequently changing source code:

FROM node:22-alpine

WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci

COPY . .
RUN npm run build

A source change can then reuse the dependency layer on both architectures instead of running npm ci from scratch.

Check the Dockerfile for architecture assumptions

The workflow can request both platforms, but every image and binary in the Dockerfile must support them.

  • Use base images that publish both AMD64 and ARM64 variants.
  • Don't download an AMD64-only binary into the ARM64 image.
  • Use BuildKit's TARGETARCH argument when an install URL or build command differs by architecture.
  • Run integration tests against each architecture when native dependencies or compiled code are involved.

For example:

ARG TARGETARCH
RUN curl -fsSL "https://example.com/tool-linux-${TARGETARCH}" -o /usr/local/bin/tool \
  && chmod +x /usr/local/bin/tool

With the workflow configured this way, one push produces a native AMD64 image, a native ARM64 image, and the manifest list that lets Docker choose between them automatically.

Start building with Depot
in minutes