Github Actions provide the ability to define a build workflow based on Github repository events. The workflow steps are defined as yaml and can be triggered by various events, including a code push, branch, or tagging in the repository.
In this article, I will show how to define workflow steps that build and push a multi-platform image (amd64,arm64,arm32) with manifest index to the Github Container Registry. This is enabled by using ‘docker buildx’ and QEMU software emulation for the various target architectures.
We will be creating a Github Action workflow by adding a yaml manifest file into the standard ‘.github/workflows’ directory. Here is a link to my full github-actions-buildOCI-image.yml, with step-through below.
Trigger
The workflow will kick off when any new semantic tag starting with a “v” (e.g. v1.0.1) is pushed to the repository.
on: push: tags: ['v*']
Checkout Code
Checkout the Github repository.
- name: Check out repository code uses: actions/checkout@v4
Setup QEMU and Docker buildx
As described in the Docker multi-platform with Github Actions page, add the following steps that enabled platform emulation and the Docker buildx/BuildKit that provide multi-platform builds.
# QEMU for software emulation of multiple platforms - name: Set up QEMU uses: docker/setup-qemu-action@v3 # Docker buildx/buildkit for multi-platform builds - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3
That is all you need to do for this pipeline, but if you want a deeper dive into components see my other article on doing manual multi-platform builds on your local server using QEMU/buildx.
Login context to Container Registries
We do need to establish a login context to the Github Container registry to push the images, but these value are already provided as part of the standard pipeline context.
# Github container registry credentials - name: Log in to the Github Container registry ${{ env.GH_REGISTRY }} as ${{ github.actor }} uses: docker/login-action@v2 with: registry: ${{ env.GH_REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }}
The “secrets.GITHUB_TOKEN” used for pushing to the Github Container Registry is a special variable already populated by Github.
Create labels and tags for images
Create the proper image tags and version labels for the build.
# tags and labels - name: Extract metadata (tags, labels) for image ${{ env.FULL_IMAGE_NAME }} id: meta uses: docker/metadata-action@v4 with: images: | ${{ env.GH_REGISTRY }}/${{ env.FULL_IMAGE_NAME }} tags: | type=semver,pattern={{version}} type=ref,event=pr type=ref,event=branch
Generate custom build arguments
As a customization, we want to pass along the time of the build along with the short SHA of the latest git commit. Placing these as key/value pairs on their own line will allow us to access them in later steps as ‘steps.get_buildargs.outputs.BUILD_TIME’ and ‘steps.get_buildargs.outputs.GITREF’
- name: Get fresh build arguments shell: bash run: echo -e "BUILD_TIME=$(date -u '+%Y-%m-%d_%H:%M:%S')\nGITREF=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT id: get_buildargs
Build and Publish OCI image
Finally, the multi-platform image is built using the Dockerfile and then published to its Github Container Registry. The ‘platforms‘ attribute is a CSV list of the desired architectures.
- name: build and push docker image uses: docker/build-push-action@v5 with: context: . push: true platforms: linux/amd64,linux/arm64,linux/arm/v7 tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} build-args: | MY_VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }} MY_BUILTBY=github-action BUILD_TIME=${{ steps.get_buildargs.outputs.BUILD_TIME }} GITREF=${{ steps.get_buildargs.outputs.GITREF }}
The ‘build-args’ specified above can be referenced in the Dockerfile, and per the snippet below are used to populate ‘/build.log’ inside the container.
# standard Docker arguments ARG TARGETPLATFORM ARG BUILDPLATFORM # custom build arguments ARG BUILD_TIME ARG GITREF # persist these build time arguments into container as debug RUN echo "[$BUILD_TIME] [$GITREF] building on host that is $BUILDPLATFORM, for the target architecture $TARGETPLATFORM" > /build.log
Trigger Github Workflow
This workflow is invoked by pushing a tag that looks like a semantic version. Here are the git commands for creating a tag and pushing it remotely.
# check for any changes that might need to be checked in first git status # create new tag that triggers workflow, push tag newtag=v1.0.0 git tag $newtag && git push origin $newtag
This will almost immediately create a workflow which you can view in real-time by going to your Github repository > Actions tab.
You can view the real-time progress by clicking into the task. The icon will indicate when the workflow is complete.
Validate multi-platform publish
From the Github web UI click on “Packages” > tiny-tools-multi-arch, and each image version will be listed.
The OCI manifest index details can be seen by using ‘docker manifest index’.
docker manifest inspect ghcr.io/fabianlee/tiny-tools-multi-arch:2.0.0 | head
Test a pull and run of the image by running docker like below.
# will output architecture family of running image docker run -it --rm ghcr.io/fabianlee/tiny-tools-multi-arch:2.0.0 uname -m
REFERENCES
docker, example of multi-platform github pipeline yaml
fabianlee.org, article showing how to do multi-platform builds on local server using QEMU/buildx
docker, buildx command reference
github buildkit, configuration that can be specified via buildx ‘config’ flag