A fleet of Mac runners you can run from the command line.
I design and set up self-hosted GitHub Actions runners for iOS and macOS teams, on AWS EC2 Mac dedicated hosts. Building images, deploying, scaling and recovering the fleet are all GitHub workflows you trigger with gh. You end up owning the infrastructure, the code, and the knowledge to run it.
$ gh workflow run deploy.yml -f team=iOS -f env=production ✓ Created workflow_dispatch event for deploy.yml # plan → approval → apply. Fleet now matches fleets/ios.yml
Teams shipping to Apple platforms who have outgrown their CI.
macOS is the one CI platform that can't run on commodity Linux machines. Apple licensing requires Apple hardware, and AWS only rents it by the whole machine. Getting that right is where most teams lose time and money.
Hosted macOS minutes keep adding up
Per-minute billing grows with every pull request, and the bill is hard to predict once UI tests and archives are in the pipeline.
Flat hourly cost per machine, scaled to your working week.
You need control of the toolchain
Your release can't wait for a hosted image to ship the Xcode you need, or to keep the one you still depend on.
Your own images with the exact macOS, Xcode and Ruby versions you pick.
The Mac mini shelf doesn't scale
Office machines go offline, nobody owns them, and there's no alerting when a disk fills up at 2am before a release.
Cloud machines with monitoring, alarms and automatic recovery.
Infrastructure as code, operated through GitHub.
Packer builds the macOS image, Terraform deploys it to dedicated hosts in your AWS account, and GitHub Actions workflows run both. Every machine registers itself with your runner group on boot.
GitHub github.com or GHE
Operational workflows, fleet definitions in YAML, approval gates per environment, and the runner groups your app pipelines target.
Packer images
Builds a macOS AMI from Amazon's base image with Xcode, simulators, Ruby, Homebrew tooling, the Actions runner and the CloudWatch agent.
Terraform provisioning
EC2 Mac instances, IAM roles, security groups, alarms and dashboards. Remote state in S3 with DynamoDB locking.
No AWS console. No SSH. Run a workflow.
Each operation is a GitHub workflow, so anyone on the team with the right permissions can run it from the gh CLI or the Actions tab. Changes go through code review and approval, and every run leaves an audit trail.
What you get on day one.
A proven foundation, customised for your organisation: your teams, your AWS account, your toolchain, your release calendar.
Fleet sizes live in a reviewed YAML file
Each environment lists its machine types, how many runners of each, and the image they run. Adding capacity, or trying a new Xcode on QA before production, is a pull request.
# fleets/ios.yml production: - machine: mac-m4.metal runners: 6 image: macos-26.1-xcode-27.1 - machine: mac2.metal runners: 4 image: macos-26.1-xcode-27.1 qa: - machine: mac2.metal runners: 1 image: macos-26.2-xcode-27.2 # canary
Scales with your working week
Scheduled workflows shrink the fleet when nobody is building, such as weekends, and bring it back before your team starts work.
Zero-downtime Xcode upgrades
Separate production and QA environments per team. Try a new image on QA first, then roll it out to production.
Runners that recover on their own
A watchdog reboots a runner that stops listening, with a circuit breaker after three attempts. Every job starts with fresh simulators and no stale DerivedData.
Dashboards and alarms per team
The CloudWatch agent is baked into every image and reports CPU, memory, disk and disk I/O. Each team gets a dashboard, and alarms email you before a disk fills up.
Read how it worksOne platform, many teams
Each team gets its own state, IAM role, security group, runner group, alarms and approval gates, on shared networking.
Private by default
No public IPs. IMDSv2 only. Tokens in Secrets Manager, never in code. Standard tags on every resource for cost allocation.
Idle hosts get released
An hourly workflow finds dedicated hosts with no instance and releases them once AWS's 24-hour minimum has passed.
Tested like application code
Pull requests run actionlint, packer validate, terraform test and Bats tests for the shell scripts.
You hire me, not a licence.
There's no product to subscribe to. I work with your team as a freelance consultant, set up the system in your accounts, tailor it to how you ship, and hand it over so you can run it yourselves.
Talk
A conversation about how your team builds and ships.
- Your pipelines and toolchain
- Teams, fleets and environments
- Release calendar and peaks
Build
Set up the platform in your AWS account and GitHub organisation.
- Custom macOS images
- Fleets, IAM, monitoring
- Workflows and schedules
Roll out
Move your app pipelines over, starting with QA.
- Runner labels in your workflows
- Side-by-side with existing CI
- Tuning from real build data
Hand over
Your team owns and runs it from here.
- Runbooks and architecture docs
- Training session for your engineers
- Full ownership of code and infrastructure
What the machines cost.
You pay for two things: my consultancy to deliver the solution, and the machines it runs on. AWS bills the machines directly to your account. One dedicated host runs one runner, priced per hour with a 24-hour minimum each time a host is allocated.
| Instance type | Chip | Per hour | Per day | Per month |
|---|
Savings Plans lower these rates. For machines that run all the time, a 1- or 3-year AWS Savings Plan cuts the hourly price. A common setup covers the always-on baseline with a Savings Plan and adds on-demand hosts for busy periods.
Estimate your fleet
On-demand rates. Weekdays only assumes hosts are released from Friday night to Monday morning, about 60 hours a week. Excludes storage and data transfer, which are usually small next to host costs.
Consultancy and a delivered solution
One engagement covers everything. I design the platform with you, deliver it into your AWS account and GitHub organisation, and get your pipelines running on it.
- Custom macOS images with your Xcode versions
- Fleets on dedicated hosts in your chosen region
- Scaling schedules for your working week and releases
- Rolling deployments for zero-downtime upgrades
- Dashboards, alarms and self-healing runners
- Operational workflows you run with
gh - Migration of your app pipelines
- Runbooks, architecture docs and team training
Quoted per project after a first conversation, based on your teams, fleets and pipelines.
Billed directly to your AWS account through standard AWS billing, at the rates above.
Once the system is operational, your team owns and operates it from then on. No licence, no subscription, no lock-in.
The thinking behind it, on stage.
I've presented the architecture, the trade-offs and the lessons from running it at two Swift conferences.
Alberto De Bortoli
I've been building CI/CD infrastructure on macOS hardware since 2020. Along the way I learned what it takes to make Apple build machines scale, stay observable, and recover on their own.
Now I help teams build their own in-house solution on top of that knowledge, tailored to their AWS account, GitHub organisation, toolchain and release calendar.
Connect on LinkedInCommon questions.
Something else on your mind? Ask me on LinkedIn.
Why not use GitHub-hosted macOS runners?
They're a good start. Once you run many builds a day, per-minute pricing usually costs more than renting the machines, and you can't pick your exact Xcode versions or keep machines tuned to your project. The estimator above gives a first idea, and we'll work out your break-even point together.
Why dedicated hosts?
Apple's macOS licence requires Apple hardware, so AWS rents each Mac as a whole physical machine on a dedicated host. That also means no noisy neighbours and consistent build times.
What does the 24-hour minimum mean for us?
AWS charges each Mac host for at least 24 hours once it's allocated. The scaling schedules and the host-release workflow are designed around this, so you don't pay for hosts you briefly needed.
Do we need a specific GitHub plan?
No. Self-hosted runners work with every GitHub plan, on github.com and on GitHub Enterprise Server. On GitHub Team and Enterprise, runner groups can also keep each team's runners separate. On other plans, jobs pick their runners by label.
Can we lower the AWS bill?
Yes, in two ways. Scheduling releases hosts when nobody is building, such as weekends. For the part of the fleet that runs all the time, a 1- or 3-year AWS Savings Plan lowers the hourly rate. We'll pick the mix that fits your usage.
Which regions can we use?
Any AWS region that offers EC2 Mac instances. Availability of each instance type differs by region, as the pricing table shows. I'll recommend a region based on your team's location and the instance types you need.
What happens after the engagement ends?
Your team owns and operates the system from then on. You keep the repository, the images and the infrastructure, along with runbooks and training, and you pay AWS directly through your usual AWS billing.
Let's size your fleet.
Send me a message on LinkedIn. A short conversation is enough to tell whether self-hosted macOS CI makes sense for you, and roughly what it would cost.
- How many iOS / macOS engineers you have
- Roughly how many CI builds you run per day
- What you use for CI today