Build multi-arch Docker images in GitHub Actions

Publishing images to GitHub Packages

Let’s say we have a repository that includes a Dockerfile. We can utilize a GitHub workflow to:

  • Check out the repository
  • Log in to the GitHub Container Registry (ghcr.io)
  • Extract metadata
  • Build and push the Docker image to our specified registry
name: Create and publish a Docker image

on:
  push:
    tags:
      - 'v*'

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build-and-push-image:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

    steps:
      - name: Checkout repository
        uses: actions/checkout@v3

      - name: Log in to the Container registry
        uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata (tags, labels) for Docker
        id: meta
        uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}

      - name: Build and push Docker image
        uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}

Build and publish multi-arch images

Depends on the type of the host machines, images can be built for multiple platforms, e.g. linux/amd64, linux/arm64/v8, etc.

Set up QEMU and Buildx

The next thing is to enable QEMU. Basically it allows GitHub action host machine to emulate different architectures.

QEMU is a generic and open source machine & userspace emulator and virtualizer. QEMU is capable of emulating a complete machine in software without any need for hardware virtualization support.

buildx is a Docker CLI plugin for extended build capabilities with BuildKit .

--- a/.github/workflows/build-publish-image.yml
+++ b/.github/workflows/build-publish-image.yml

@@ -20,6 +22,12 @@ jobs:
       - name: Checkout repository
         uses: actions/checkout@v3

+      - name: Set up QEMU
+        uses: docker/setup-qemu-action@v2
+
+      - name: Set up Docker Buildx
+        uses: docker/setup-buildx-action@v2

Specify the platforms in docker/build-push-action

In docker/build-push-action , we can set platforms to a list of target platforms to build, separated by comma. In our example, we only needs to build linux/amd64 for all amd64 machine, and linux/arm64/v8 for Apple Silicone MacBooks.

--- a/.github/workflows/build-publish-image.yml
+++ b/.github/workflows/build-publish-image.yml
@@ -37,6 +45,7 @@ jobs:
         uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
         with:
           context: .
-          push: true
+          push: ${{ github.event_name != 'pull_request' }}
+          platforms: linux/amd64,linux/arm64/v8
           tags: ${{ steps.meta.outputs.tags }}
           labels: ${{ steps.meta.outputs.labels }}

View them on GitHub Packages

Once the GitHub workflow has been triggered and the images have been built successfully, we can see them from the OS/Arch tab.

Final thoughts

As I just discovered, linux/arm64/v8 is normalized as just linux/arm64.

docker image inspect ghcr.io/imfing/keras-flask-deploy-webapp --format '{{.Os}}/{{.Architecture}}'
linux/arm64

References