We Built Our Own CI Fleet on a Mac Mini

— By Christopher Lynch

Our build checks were queuing for rented cloud computers while a Mac mini sat in our office with capacity to spare. So we turned the mini into a self-managing fleet: fresh build machines in seconds, a five-minute promise, and nothing standing idle. Here is how it works, in plain language.

Every time we change the code behind one of our products, an automated battery of checks wakes up. Thousands of tests run. Databases are built, exercised, and taken apart. Builds are produced and booted to prove they start. Only when all of it passes does a change get anywhere near the people who use our software.

Those checks need computers to run on — "runners," in the trade. And for a long time, we had the same two choices every small software team knows well.

The two bad options

Option one: rent runners from GitHub's cloud, by the minute. It works, but bursts of work queue up behind everyone else's bursts. We measured waits as long as 19 minutes for a runner to become available — while a capable computer sat in our office with capacity it had no way to offer.

Option two: keep a machine of our own standing by around the clock. That was our previous setup: one large virtual machine living on our office Mac mini, holding eight permanent runners. It reserved 36 gigabytes of the mini's memory day and night, whether or not a single test was running.

Neither option respects the shape of the work. Software testing arrives in bursts. The honest answer to a burst is a fleet that grows when work arrives and vanishes when it doesn't.

The question we asked

The computer in question is a Mac mini — Apple's small desktop machine, with an M5 Pro chip, 18 processor cores, and 64 gigabytes of memory. It already lives in our office. It already serves our local AI model. Between bursts, much of it was simply waiting.

So we asked: what if the computer already in the office is the fleet?

What we built

The system we built on the mini works like this, in plain terms.

!Diagram of the fleet's shape: code changes queue on GitHub; the office Mac mini stamps out one fresh build machine per job under a supervisor that defends a memory reserve; a job that waits more than five minutes at a genuinely full house is re-sent to GitHub's cloud runners

The whole system on one page: work arrives from GitHub, the mini does everything it safely can, and the cloud takes only the overflow.

A master machine, saved once. We built one complete build computer — operating system, tools, database servers, language runtimes, everything a check could need — and saved it as a master image. Nothing gets installed when a job arrives; the preparation happened once.

Fresh machines in seconds. When a job shows up, the mini stamps out a copy of that master. The copy shares the master's disk until it changes something, so a brand-new, fully equipped build computer exists in under a second, and its runner is online in about eight seconds, measured. Each machine takes exactly one job — in total isolation, with its own private databases and services — and is then deleted. Its registration with GitHub removes itself. Nothing accumulates, and nothing goes stale.

A careful landlord. A small supervising program checks every ten seconds how much the mini can safely carry right now — free memory, processor load, what else the machine is doing — and admits work to match. It always defends a reserve for the person at the desk and for the AI model the mini also serves. And if the numbers can't be read, it admits nothing. Doing nothing is the default answer to confusion, everywhere in this system.

!Flow diagram of one job's life: a job arrives, the supervisor checks the mini can safely carry it, a fresh machine is cloned from the master image in under a second, the job runs alone, and the machine is deleted when the job finishes

One job, one fresh machine, nothing left behind — and a supervisor that would rather wait than guess.

Self-healing. Machines occasionally finish a job and fail to check out cleanly. Early on, one stuck machine could quietly block its whole class of work behind it. The system now watches for exactly that shape — a finished job whose machine never reported back — and clears the slot on evidence, in about five or six minutes. That failure mode used to be able to linger for up to three hours.

A five-minute promise. If a job has waited more than five minutes and the mini is genuinely full, the run is automatically re-sent to GitHub's cloud runners. Local first, cloud as the pressure valve. We deliberately put no spending cap in the way of a needed burst; instead, every burst is logged with its full circumstances, and a weekly report lets a human judge whether each one was warranted. Visibility, not throttling.

!Decision flowchart for the five-minute promise: a waiting job that has waited more than five minutes while the mini is genuinely full is re-sent to GitHub's cloud runners and the burst is logged for a weekly human review; otherwise it keeps its place in line

The five-minute promise as a flowchart: local first, cloud as the pressure valve, and every burst written down for a person to judge.

What it carries today

This is not a science project. Our own products run on it.

The complete test matrix for OpenWater, our dive-trip platform, is 17 jobs: unit tests, integration tests, end-to-end tests, browser checks, and production builds. The fleet ran that entire matrix, start to finish, on throwaway machines stamped out inside the mini — the latest production pass took 64 minutes, all 17 jobs green, every job on a local guest, no cloud help. Production CI runs through the same fleet — with the cloud as its pressure valve under the same five-minute rule, so a guest that misbehaves costs minutes, not the release.

And this morning, we retired the old system. The big always-on virtual machine and its eight permanent runners are gone, because the fleet made them unnecessary. The memory they held around the clock went back to the work in front of the machine.

Why we are telling you this

Because this is how we work — and it is the same work we do for clients.

When a tool doesn't fit, we don't rent a bigger workaround or hold a process together by hand. We understand the problem all the way down, build the thing that fits, prove it on our own products first, and keep humans in charge of the decisions that matter. In this system, machines decide where work runs. People still decide what the work is worth.

Most businesses we meet have their own version of the runner problem: a subscription that almost fits, a report somebody assembles by hand every week, a tool the team works around instead of with. Those are solvable problems, and they are our favorite kind.

If you have one, talk with our team. We would be glad to take a look.

Intuitive Context is a founder-led technology practice, founded in 2014. We build practical software and AI systems for businesses — and, as you can see, for ourselves.

Loading…

If this page stays blank, your browser may be too old to run it. Open this link in a recent Chrome or Safari, or update your browser.

Questions? [email protected]