We use cookies to understand how people use Depot.
🚀 Now available: macOS GitHub Actions Runners
← All Posts

How to build multi-platform Docker images in GitHub Actions

Written by
kyle
Kyle Galbraith
Published on
27 October 2023
Building multi-platform, also known as multi-architecture, Docker images in GitHub Actions is becoming a necessity. See how to build them in GitHub Actions.
How to build multi-platform Docker images in GitHub Actions banner

In this post, we will focus on building multi-platform Docker images, as well as Arm images, in GitHub Actions.

Multi-platform images and Arm support

By default, Docker images are built for the architecture of the machine running the build. If you build an image on an Intel machine, the image will be built for Intel. If you build an image on an Arm machine, the image will be built for Arm.

If you want to build an image for a different architecture than the machine you are building on, you can specify the --platform flag during a Docker build. For example, if you are building on an Intel machine and want to build an Arm image, you can specify --platform linux/arm64.

docker build --platform linux/arm64 .

A Docker image can also be built for multiple architectures simultaneously. This produces what is often referred to as a multi-platform or multi-architecture image. If you want to build a multi-platform Docker image for both Intel and Arm, you can specify multiple platforms in the --platform flag.

docker build --platform linux/arm64,linux/amd64 .

A multi-platform Docker image build triggers two builds, one for each architecture, and produces a single image that supports both platforms. But, to build that image, one of the architectures must be emulated using qemu emulation. If this is a multi-platform image built in CI, like GitHub Actions, the Arm portion (i.e., linux/arm64) will be emulated.

Alternatively, there is the option to configure docker buildx build to use multiple builders, one for each platform. This method removes the need to emulate the non-host machine architecture. But, in exchange, you have to run your own native builders. For more details, we have a blog post on running your own builder instances.

Building multi-platform Docker images in GitHub Actions

This section assumes you know the basics of building Docker images in GitHub Actions. If you are new to building Docker images in GitHub Actions, we have a blog post that covers the basics of building Docker images in GitHub Actions.

To build a multi-platform Docker image in GitHub Actions, we must configure QEMU emulation and buildx in our workflow.

name: Build multi-platform Docker image
 
on:
  push: {}
 
jobs:
  build-with-docker:
    name: Build multi-platform Docker image
    runs-on: ubuntu-20.04
    steps:
      - uses: actions/checkout@v3
      - uses: docker/setup-qemu-action@v3
      - uses: docker/setup-buildx-action@v3
      - uses: docker/build-push-action@v5
        with:
          context: .
          platforms: linux/amd64,linux/arm64
          cache-from: type=gha
          cache-to: type=gha,mode=max

Three steps in our workflow are worth noting:

  1. docker/setup-qemu-action configures QEMU emulation for the Arm portion of our multi-platform Docker image build. This is required for multi-platform Docker image builds in GitHub Actions, as the hosted runners are Intel machines.

  2. docker/setup-buildx-action configures buildx for our workflow. It's required for multi-platform Docker image builds in GitHub Actions that use docker/build-push-action.

  3. docker/build-push-action builds and pushes our Docker image. We specify the platforms argument to build our image for both Intel and Arm architectures. If we wanted to push our image to a registry, we could add an additional step above docker/build-push-action to login to our registry and then specify the push argument. We specify the cache-from and cache-to parameters to store the Docker layer cache via the GitHub Cache API.

- uses: docker/login-action@v3
  with:
    username: ${{ secrets.DOCKERHUB_USERNAME }}
    password: ${{ secrets.DOCKERHUB_TOKEN }}
- uses: docker/build-push-action@v5
  with:
    context: .
    platforms: linux/amd64,linux/arm64
    push: true
    tags: <your-repo>:<your-tag>
    cache-from: type=gha
    cache-to: type=gha,mode=max

If you wanted to build a Docker image for only Arm, you would change the platforms argument to just linux/arm64 but keep the rest of the workflow to use QEMU emulation.

This is a basic example of building multi-platform Docker images in GitHub Actions with QEMU emulation. It's functional, but it has limitations that are worth noting:

  1. The Arm portion of the build is emulated using QEMU. We've seen up to 30x speedups when building on native Arm machines vs emulating Arm in CI via our benchmarks like Temporal's multi-platform build.
  2. The GitHub Cache API only supports a maximum size of 10 GB for the entire repository, each architecture will have to share this 10 GB limit.
  3. Loading and saving cache over networks is slow, meaning the loading and saving could negate any performance benefits of using the cached layers for simple image builds
  4. The cache is locked to GitHub Actions and can't be used in other systems or on local machines

An alternative to emulation is to run your own GitHub Action runners on Arm instances. This comes with a significant increase in infrastructure and complexity you must manage.

A managed solution

We built Depot to eliminate the pain of emulation and other limitations above, not only in GitHub Actions but in all CI providers.

Depot is a remote container build service that runs an optimized version of BuildKit on native cloud instances for Intel and Arm. We manage your Docker layer cache on fast NVMe SSDs that make your cache instantly available across builds. The layer cache can be used from your CI build in GitHub Actions and your local machine when you use depot build.

Each build runs on a dedicated single-tenant BuildKit machine, using native Intel or Arm CPUs. Each instance includes 16 CPUs, 32GB memory, and the persistent layer cache can be expanded up to 500 GB.

Temporal multi-platform Docker image build benchmark

The combination of faster machines, native CPUs, and instant caching across builds results in faster builds. We've seen up to 30x speedups when building on native Arm machines vs emulating Arm in CI via our benchmarks like Temporal's multi-platform build or Mastodons multi-platform build.

Depot effectively provides on-demand access to one or more remote BuildKit builders and automatically routes each build platform to the machine best suited to build for that platform.

If you are interested in trying out Depot in your GitHub Actions workflow, check out our GitHub Actions integration guide and get started with Depot.

Your builds have never been this quick.
Start building