Faster, cheaper, messier: lessons from our switch to self-hosted GitHub Actions

At The Guardian, like many engineering teams, we rely heavily on CI/CD pipelines to keep our software fresh, secure, and reliable for our users. Our workflows hinge on GitHub Actions, so when things go wrong or get expensive, the impacts are felt quickly. This is the story of why we moved away from GitHub-hosted runners to using our own self-hosted runners, what the process involved, and what we’ve learned along the way.

Simone Smith

Published on Monday, 22 December 2025

GitHub   iOS   Apple   Apps  

Female technician using digital tablet examining in server room
Female technician using digital tablet examining in server room Getty Images

At The Guardian, like many engineering teams, we rely heavily on CI/CD pipelines to keep our software fresh, secure, and reliable for our users. Our workflows hinge on GitHub Actions, so when things go wrong or get expensive, the impacts are felt quickly. This is the story of why we moved away from GitHub-hosted runners to using our own self-hosted runners, what the process involved, and what we’ve learned along the way.

What Did We Do Before?

For a long time, all our automation ran on GitHub-hosted runners. The set-up was minimal: we simply needed to configure our workflow files to run on the runners of our choice (mostly macOS runners, since we work on an iOS app, but where possible we used Linux runners to keep costs down) and then we could let GitHub orchestrate the infrastructure. The only real maintenance involved was keeping an eye out for new macOS versions as Apple released them, and then updating the relevant configuration. Sometimes, GitHub took a little longer to roll out those updates than we would have liked, but overall, things ran pretty smoothly.

Why Did We Move?

Despite the generally low maintenance cost, there were a few pain points with GitHub-hosted runners, which encouraged us to look for alternatives:

We’d had some success using Xcode Cloud for a while in 2023/2024, but when it stopped working out of the blue for us in March 2024 we had to restore our GitHub Actions workflows in order to unblock our releases.

Other options could have included third-party mobile CI solutions such as Runway and Bitrise, but we wanted to explore our in-house opportunities first.

Finally, we experimented with cutting our costs on GitHub-hosted runners as much as we could, trying to speed up our builds by making changes such as skipping unit tests on draft PRs, optimising our use of the GitHub cache for our Swift packages, and moving to larger runners to see if the cost-saving from the increased speeds outweighed the higher per-minute cost, but none of these solutions made a sufficient difference to our monthly bills.

As part of the above experiments we also did some work on visualising our build times. The graph below dates from the start of the experimentation process, so it doesn’t capture any of the improvements we did manage to make. However, it provides a revealing insight into how exposed we were to issues outside our control - the huge spike in May was caused by a GitHub outage that we just had to wait for them to resolve.

The daily duration of running unit tests on GitHub-hosted runners.

The daily duration of running unit tests on GitHub-hosted runners. Illustration: Simone Smith

Why Self-Hosting?

We had an unused Mac Mini in the office, which significantly lowered the barrier for experimenting with a self-hosted runner. We decided to attempt running our GitHub actions locally and see whether this would be a better solution for us.

Setting It Up

The initial setup involved following GitHub’s official self-hosted runner instructions. In brief, this involved installing the runner software, authenticating it against our repo, and updating our workflows to run on our Mac Mini rather than GitHub-hosted machines. We also needed to set up remote access for developers on our team to manage the machine, so that even though it’s a self-hosted machine, we don’t have to actually be physically present to use it.

In reality, the experience was a bit more trial-and-error than plug-and-play. Transitions like configuring DerivedData folders for Xcode builds and cleaning up artifacts between jobs required extra care. Since our local runners aren’t ephemeral (unlike cloud instances), we had to devise strategies for keeping the workflow runs clean - deterministic DerivedData folder names for each run, post-job cleanup steps, regular deletion of old Xcode versions and simulators on the machine, and ensuring that the keychain was deleted after failed deployment-related actions, to prevent future failures.

How’s It Going?

The Positives

The daily duration of running unit tests on GitHub-hosted runners.

The average duration of running unit tests and uploading builds on GitHub-hosted runners compared to self-hosted runners. Illustration: Simone Smith

The Negatives

Learnings for the Future

Switching to self-hosted runners wasn’t frictionless, but the speed, cost, and control gains justify the effort. If you’re wrestling with sluggish pipeline builds or unpredictable updates, it’s a path worth exploring, just be prepared for a bit more hands-on maintenance than is required with GitHub-hosted runners.

Continue reading

Day in the Life: Simon Adcock