Simple Multi-Arch Docker Builds

2 minute read

I've been doing multi-arch builds for a while as many images I use run on an array of architectures at home, least not my small farm of raspberry pi's. I've recently decided to try and simplify my build set up and came across `buildx` as well as deciding to migrate to GitHub Actions.

Using 'docker buildx build'

As of writing this post, 'docker buildx' is an experimental feature but allows you to build an image in an array of architectures with a single command that effectively wraps what `docker manifest` does for you. Here's a simple example:

FROM alpine:3.12

ENTRYPOINT ["echo", "hello!"]
docker buildx build \
  --tag hello:latest \
  --tag hello:1.0 \
  --platform linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8 .
You can even have `buildx` push your built images (given you're logged into a registry) with an additional "push" flag in the command:
docker buildx build \
  --push \
  --tag hello:latest \
  --tag hello:1.0 \
  --platform linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8 .

Building From Source

Previously, using my Syncthing image as an example. I'd do multiple docker builds, one for each architecture, by passing in the architecture ("$ARCH") as a --build-arg, and within the Dockerfile I'd download a version of Syncthing for the specified architecture I was building.

With buidx, the build runs as a single command so I'm not able to control which architecture version of the app to download and install within the Dockerfile. Building from source simplifies this and is arguably a better, more agnostic thing to do.

Tying It Together With GitHub Actions

I've recently been starting a migration to GitHub Actions from a mixture of CircleCI and Travis. Neither are bad options, but GitHub Actions does have a few features that really interest me. Path matching is the first, effectively allowing easier "mono" repo support (or at least repo's with multiple concepts), or simply ignoring certain files from triggering a build (such as README.md's).

The second feature is the easy of using community actions, of which both python and docker buildx were easy for me to hook up. Granted GitHub Actions isn't as fast when comparing equivalent builds, but I can live with it. Here's a snippet taken from my build file:

  build:
    runs-on: ubuntu-latest
    needs: [lint, test]
    if: github.ref == 'refs/heads/main'
    steps:
      - uses: actions/[email protected]

      - name: variables
        run: curl --silent https://api.github.com/repos/syncthing/syncthing/releases/latest | jq -r '.tag_name' > version

      - name: build dependencies
        uses: crazy-max/[email protected]
        with:
          version: latest

      - name: docker hub login
        run: echo "$" | docker login -u "$" --password-stdin

      - name: build
        run: |
          docker buildx build \
            --push \
            --tag robertbeal/syncthing:latest \
            --tag robertbeal/syncthing:$(cat version) \
            --build-arg=VERSION="$(cat version)" \
            --build-arg=COMMIT_ID="$GITHUB_SHA" \
            --platform linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8 .

Updated: