Menu
Home Articles About Work With Me
Close up of a complex computer motherboard ready for a second life
DevOps Apr 14, 2026 • 11 min read

Your GitHub Actions Bill Is Embarrassing: How a $15 Relic Fixed Mine

A step-by-step guide to turning ancient hardware into a self-hosted GitHub Actions runner with Alpine Linux, Docker, and Portainer. Save hundreds on CI costs.

Share:
Lee Foropoulos

Lee Foropoulos

11 min read

Continue where you left off?
Text size:

Contents

I got my GitHub Actions bill last month and did a genuine double-take. Not the performative kind where you pretend to be surprised. The real kind, where your neck actually snaps back and you whisper an expletive at your monitor.

For a site that builds maybe twice a week, I was burning through CI minutes like they were complimentary breadsticks. The culprit? A 14-language static site build that chews through compute like a teenager at an all-you-can-eat buffet.

Something had to change. And it did. In the form of a piece of hardware so old it probably remembers Y2K.

2,000+
free GitHub Actions minutes burned monthly on a site that deploys twice a week
My CI bill was running higher than my bar tab on a Friday night. And I drink silver tequila with tonic and a splash of lime, which is not a cheap habit.

Meet Hercules (Yes, We Named the Server)

Every good origin story needs a hero you don't expect. Ours is an Intel Atom D2700 motherboard from 2011. Dual-core. 1.86 GHz. The kind of processor that makes your smartphone look like a supercomputer.

I found it sitting in a pile of parts that could generously be described as "electronics graveyard chic." Two internal drives, a case that weighs more than my dog, and enough dust to build a small civilization.

Finance and money planning with notebook and calculator on desk
Meet Hercules. Fifteen dollars worth of silicon that's about to save hundreds in CI costs. The dust is free.

The specs are laughable by 2026 standards:

Hercules Hardware Specs

CPU: Intel Atom D2700 (2 cores, 1.86 GHz, released 2011) RAM: 4 GB DDR3 Storage: Two internal drives (one for the OS, one gathering emotional dust) Network: Gigabit ethernet (the only spec that actually matters for this job) Cost: About $15 at a swap meet, if you're being generous

But here's the thing about CI runners: they don't need to be fast. They need to be available. And free. Hercules is both of those things 24 hours a day.

Alpine Linux: When Your OS Weighs Less Than Your Wallpaper

For an underpowered box like this, every megabyte counts. Ubuntu? Too bloated. Debian? Still too much. We went with Alpine Linux, the espresso shot of operating systems.

~130
MB for a full Alpine Linux installation. Ubuntu needs 4,000+.

Alpine boots in seconds, uses almost no RAM at idle, and comes with apk, a package manager so fast it makes apt look like it's running through molasses. The entire OS is basically a busybox wrapper with attitude.

The installation process is refreshingly simple: boot from USB, run setup-alpine, answer some questions, reboot. Done.

Well. "Done."

The installation was simple. Everything that came after was a masterclass in creative problem-solving, questionable language, and aggressive Googling.

The Headless SSH Shuffle (or: Murphy's Law, Hardware Edition)

Here's where it gets fun. The D2700 was going to live in a closet with an ethernet cable. No monitor. No keyboard. Just power and network. The problem? I was configuring it at my desk, where there was a monitor but no ethernet.

Hiccup #1: No SSH out of the box. Alpine doesn't install openssh by default unless you tell it to during setup. I missed that. And without network, you can't apk add anything. Classic chicken-and-egg.

Hiccup #2: The USB stick musical chairs. This machine has 10 drives visible to the system (two internal drives with multiple partitions, plus the boot USB). Finding the right /dev/sd?1 to mount was like a game show where every door leads to an EFI partition.

Fiber optic and ethernet cabling in a network infrastructure setup
The ethernet cable situation was "temporary." It's been three weeks.

The solution: Write a self-executing boot script that installs SSH the moment the box gets internet. Drop it into /etc/local.d/ (Alpine's equivalent of rc.local), and it runs on every boot until it succeeds, then deletes itself.

bash
1cat > /etc/local.d/setup-d2700.start << 'EOF'
2#!/bin/sh
3# Wait for network, install SSH, delete self
4if ! which sshd >/dev/null 2>&1; then
5  apk update && apk add openssh
6  echo "PermitRootLogin yes" >> /etc/ssh/sshd_config
7  rc-update add sshd default
8  rc-service sshd start
9fi
10rm -f /etc/local.d/setup-d2700.start
11EOF
12chmod +x /etc/local.d/setup-d2700.start

Shut down. Unplug monitor. Carry the box to the closet. Plug in ethernet. Power on. Wait two minutes. Check router DHCP list. SSH in. Fist pump.

Pro Tip: Find Your Headless Box

After you plug the box in, it'll grab a DHCP address from your router. Check your router's admin page (usually 192.168.1.1) under "Connected Devices" or "DHCP Clients." Look for the hostname you set during setup-alpine. Reserve that IP so it never changes.

Docker Saves the Day (Because Alpine Hates .NET)

Now here's where I hit a wall that cost me a solid hour of sanity. The GitHub Actions runner binary requires .NET Core 6.0. .NET requires glibc. Alpine uses musl. These two C libraries get along like cats and vacuum cleaners.

I tried gcompat (Alpine's glibc compatibility layer). The runner binary spat out errors about __isnan symbols not found. I tried the runner's own installdependencies.sh. It replied, "Can't detect current OS type." Cold.

The Alpine-glibc Trap

The GitHub Actions runner does NOT run natively on Alpine Linux. Don't waste time with gcompat or manual .NET installs. Go straight to Docker. You'll save yourself an hour of frustration and a significant amount of hair.

The fix? Run the GitHub Actions runner inside a Docker container that already has glibc, .NET, and everything else it needs.

bash
1# Generate a fresh runner registration token
2TOKEN=$(curl -s -X POST \
3  -H "Authorization: token $PAT" \
4  -H "Accept: application/vnd.github+json" \
5  "https://api.github.com/repos/OWNER/REPO/actions/runners/registration-token" \
6  | jq -r '.token')
7
8# Launch the runner in Docker
9docker run -d \
10  --name github-runner \
11  --restart unless-stopped \
12  -e REPO_URL=https://github.com/OWNER/REPO \
13  -e RUNNER_TOKEN=$TOKEN \
14  -e RUNNER_NAME=atom-d2700 \
15  -e LABELS=self-hosted,linux,x64,d2700 \
16  -e DISABLE_AUTO_UPDATE=true \
17  -v /var/run/docker.sock:/var/run/docker.sock \
18  myoung34/github-runner:latest

Thirty seconds later:

1√ Connected to GitHub
2√ Runner successfully added
3Listening for Jobs
Terminal screen with command line interface and shell output
The most satisfying three lines of text I've seen since "Transaction: Approved" on a Friday night.
$0.00
monthly CI cost after switching to the self-hosted D2700 runner

The --restart unless-stopped flag means the runner survives reboots. The myoung34/github-runner image handles all the .NET/glibc dependencies. Your ancient Alpine box just runs Docker and stays out of the way.

The Emergency Brake: One Variable to Rule Them All

Running CI on a 2011 Atom processor comes with an obvious risk: what if it's too slow? What if it crashes? What if your cat knocks the ethernet cable out?

The fix is a GitHub Actions variable that lets you toggle between your self-hosted runner and GitHub's cloud runners without changing any code.

In your workflow files, replace:

yaml
runs-on: ubuntu-latest

With:

yaml
runs-on: ${{ vars.RUNNER_TYPE || 'ubuntu-latest' }}

Then in your repo's Settings > Variables > Actions, add:

  • Name: RUNNER_TYPE
  • Value: self-hosted,linux,x64,d2700

Jobs route to your box. D2700 goes down? Delete the variable. Jobs fall back to GitHub's runners instantly. No commits, no deployments, no drama.

Keep Deploy on GitHub

If you're using GitHub Pages with actions/deploy-pages, keep that job on ubuntu-latest. It needs OIDC tokens that work best on GitHub's infrastructure. Route your CI validation and PR checks to the self-hosted runner instead.

Bonus: Portainer Edge Agent (Manage It All From Your Couch)

SSH is fine, but you know what's better? A GUI that shows you everything running on the box from your laptop's browser.

Portainer has a feature called "Edge Agent" that lets remote Docker hosts phone home to a central Portainer instance. The D2700 initiates the connection outbound (no ports to open, no firewall rules), and you get full container management from your main machine.

bash
1docker run -d \
2  --name portainer-edge-agent \
3  --restart unless-stopped \
4  -v /var/run/docker.sock:/var/run/docker.sock \
5  -v portainer-agent-data:/data \
6  -e EDGE=1 \
7  -e EDGE_ID=<your-edge-id> \
8  -e EDGE_KEY=<your-edge-key> \
9  -e EDGE_INSECURE_POLL=1 \
10  portainer/agent:latest

The Edge ID and Key come from Portainer's "Add Environment" wizard. Pick "Edge Agent Standard" for the most secure option: the tunnel only opens on-demand when you click into the environment.

Data dashboard with charts and automated monitoring analytics
Your $15 server, managed from the comfort of a browser tab. The future is now, and it's running on hardware from 2011.
The D2700 now runs a GitHub Actions runner and a Portainer agent, uses 0.4 GB of its 3.8 GB RAM, and idles at nearly zero CPU. It's the most relaxed piece of hardware in my house.

The Bottom Line

Here's what we built, what it cost, and what it saves:

ItemCost
Atom D2700 hardware~$15 (swap meet find)
Alpine LinuxFree
DockerFree
GitHub Actions runner imageFree
Portainer Edge AgentFree
Electricity (~10W idle)~$1/month
Total monthly cost~$1

Versus GitHub Actions at $0.008/minute for Linux runners, which adds up to $15-30/month for even moderate usage. That's a payback period of... about one month.

The D2700 won't win any speed contests. A full 14-language build takes longer than it would on GitHub's cloud runners. But for CI validation, PR checks, and English-only builds? It's more than enough. And the price is unbeatable.

IoT connected devices on a workbench representing DIY tech projects
When your CI bill goes from "cringe" to "essentially free" and all it took was a dusty box from a closet.

Next up: I've got an old i7 laptop that's about to get the same treatment. More cores, more RAM, same zero-dollar CI bill. The closet server farm grows.

Your Self-Hosted Runner Checklist 0/7
How was this article?

Share

Link copied to clipboard!

You Might Also Like

Lee Foropoulos

Lee Foropoulos

Business Development Lead at Lookatmedia, fractional executive, and founder of gotHABITS.

🔔

Never Miss a Post

Get notified when new articles are published. No email required.

You will see a banner on the site when a new post is published, plus a browser notification if you allow it.

Browser notifications only. No spam, no email.

0 / 0