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
.
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.
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.
Three steps in our workflow are worth noting:
-
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. -
docker/setup-buildx-action
configuresbuildx
for our workflow. It's required for multi-platform Docker image builds in GitHub Actions that usedocker/build-push-action
. -
docker/build-push-action
builds and pushes our Docker image. We specify theplatforms
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 abovedocker/build-push-action
to login to our registry and then specify thepush
argument. We specify thecache-from
andcache-to
parameters to store the Docker layer cache via the GitHub Cache API.
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:
- 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.
- 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.
- 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
- 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.
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.