My website is hosted on a RPi4, built using GitHub Actions deployed to the GitHub Container Registry (ghcr.io) and updated using watchtower. This has been a relatively painless process, but there is an issue...
It takes almost 10 minutes to complete the build! A local build on my dev laptop takes way less time than that!
The problem here is that the default build machines available to GitHub Actions are based on the X86 architecture, and the RPI4 I am hosting the website on is based on the Arm architecture. In order that the built containers are compatible with the Arm architecture, the Docker build process needs to make use of the platforms
directive to indicate that we are targeting a linux/arm64
target. Check out this line in the projects workflow configuration.
While this works, it is not optimal as the GitHub Actions runners are currently limited to X86 architecture hosts. Hopefully this will change soon as GitHub and Azure start rolling out Arm based servers, but the problem presents me with the ideal opportunity to explore the use of self-hosted runners.
As we are targeting an Arm host, the X86 build machine needs to emulate the requested Arm architecture through use of QEMU. This is a functional but relatively slow and processor intensive process. It would be much better if the underlying processor architecture on the build machine matched that of the target machine.
With GitHub Actions, you have the option to self-host a runner. The process of setting up the runner is fairly simple, ig you want to do it manually, simply follow the instructions on the GitHub documentation.
However, there is a better way. Using an Ansible playbook to automate the process of setting up the runner. A quick search on Ansible Galaxy locates an Ansible Collection that does exactly what I need.
The only slightly tricky part of the process it setting up the Personal Access Token (PAT) that is required to authenticate the runner with GitHub. You just need to ensure that the PAT has the correct permissions by following the instructions on setting up the token scope.
Once the PAT has been created, store it in the ansible-vault encryped secrets file so that it can safely be stored in the public repository.
My playbook is available on GitHub if you would like to see how simple this approach is. The great thing about using Ansible to manage the runners is that if I need to deploy more runners in the future, I can simply update the inventory file and run the playbook again.
Deploying the runners is now a quick, easy and most importantly a repeatable process:
# Install the dependencies
ansible-galaxy install -r requirements.yml
# Install the GitHub runner
ansible-playbook -i inventory -e @secrets.enc --ask-vault-pass github-runner.yaml
Once completed, you should see the results in your github actions dashboard:
The image shows two runners, the top one was created by the ansible script, with the bottom being my first attempts at a manual installation.
So - what are the results. Can a RPi4 building the site be quicker than a server grade x84 system? The answer in my workflow is a resounding yes!
Build time is reduced from almost 10 minutes to 3.5 minutes. Not only is this a significat improvement, is also stops me eating into my free build minutes on GitHub. Your mileage may vary depending on how complex your build process is, but this has proven to be a relatively simple and cheap way to improve the build process for my website.
And the cool thing... This blog post was built using the self-hosted runner!