GitHub Actions is great until you hit two walls: minutes run out on private repos, and every build happens on an x86_64 VM even when your target is ARM. If you already run a Raspberry Pi for WireGuard or Pi-hole, adding a self-hosted GitHub Actions runner costs you nothing extra in hardware and solves both problems at once. This post walks through installing, registering, and running the actions-runner as a systemd service on a Raspberry Pi, plus the security tradeoffs you need to accept before you do it.
Why run your own ARM64 runner
- GitHub-hosted minutes are limited on private repos (and cost money past the free tier); a self-hosted runner on hardware you already own is free CI capacity.
- Native ARM64 builds. GitHub does offer hosted Linux arm64 runners now, but they’re a separate (paid, org/enterprise-gated in most cases) SKU. A Pi gives you real ARM64 silicon for free, which matters if you’re building container images or binaries for Raspberry Pi, AWS Graviton, or other ARM targets — no QEMU emulation, no cross-compilation flakiness.
- Full control of the environment: pre-installed tools, cached dependencies, persistent Docker layer cache, direct LAN access to internal services (Pi-hole, a local registry, your homelab network) that a GitHub-hosted runner could never reach without a tunnel.
The tradeoff is that you now own patching, uptime, and — critically — security isolation. More on that at the end.
Requirements
- A Raspberry Pi running Raspberry Pi OS 64-bit (Bookworm or newer). The runner binary is a self-contained .NET (Mono-free) build and only ships for arm64, not armhf/32-bit — check with
uname -m, it must printaarch64. - A repository (or organization) you have admin access to, so you can generate a runner registration token.
- Enough headroom for your build workloads — a Pi 4/5 with 4-8GB RAM is comfortable for most CI jobs (linting, small container builds, Terraform plans); heavier builds will just take longer than a beefy GitHub-hosted runner.
curl,tar, and (recommended)sudoaccess to install the systemd service.
Download and install the runner
The runner ships from the actions/runner GitHub repository. At the time of writing the latest release is v2.336.0, and the ARM64 Linux tarball is actions-runner-linux-arm64-2.336.0.tar.gz. Always confirm the current version on the releases page before running these commands — GitHub ships new runner versions frequently, and the runner itself will nag you to update if you fall too far behind.
# Create a folder for the runner
mkdir actions-runner && cd actions-runner
# Download the latest ARM64 Linux runner package (check releases page for current version)
curl -o actions-runner-linux-arm64-2.336.0.tar.gz -L \
https://github.com/actions/runner/releases/download/v2.336.0/actions-runner-linux-arm64-2.336.0.tar.gz
# Verify the SHA-256 checksum (copy the hash shown next to the asset on the releases page)
echo " actions-runner-linux-arm64-2.336.0.tar.gz" | shasum -a 256 -c
# Extract
tar xzf ./actions-runner-linux-arm64-2.336.0.tar.gz
Don’t skip the checksum step just because it’s a Pi in your closet — the runner has full access to your repo’s secrets and network, so treat the binary with the same suspicion you’d apply to any other privileged agent.
Get a registration token
In your repository, go to Settings > Actions > Runners > New self-hosted runner (for an organization-wide runner, it’s Organization Settings > Actions > Runners instead). Select Linux and ARM64 — GitHub will display the exact ./config.sh command with a fresh, single-use registration token baked in. That token is time-limited (it expires after about an hour), so generate it right before you run the config step rather than copying it from an old tab.
Configure the runner
./config.sh --url https://github.com/OWNER/REPO --token AXXXXXXXXXXXXXXXXXXXXXXX \
--name pi-runner \
--labels self-hosted,ARM64,raspberry-pi \
--unattended
--url— the repo (or org) URL you got the token from.--token— the single-use registration token from the Runners page.--labels— comma-separated custom labels, no spaces. These are additive to the defaultself-hosted,Linux,ARM64labels GitHub assigns automatically based on the detected OS/arch, but it’s worth being explicit so your workflow’sruns-onis unambiguous.--unattended— skips interactive prompts, useful when you’re doing this over SSH.
Run it as a systemd service
Running ./run.sh in a terminal works for a quick test, but it dies the moment your SSH session drops. The runner package includes svc.sh, which wraps the binary in a systemd unit so it survives reboots and stays up in the background — same pattern as wrapping WireGuard in wg-quick@wg0.service.
sudo ./svc.sh install
sudo ./svc.sh start
sudo ./svc.sh status
Check it in GitHub too — the runner should show up as Idle on the Settings > Actions > Runners page within a few seconds. Useful commands going forward:
sudo ./svc.sh stop # stop the service
sudo ./svc.sh uninstall # remove the systemd unit
journalctl -u actions.runner.* -f # tail the runner's logs
Point workflows at it with labels
Any job that should run on the Pi instead of a GitHub-hosted runner just needs to match the labels you set during config.sh:
name: build-arm64
on:
push:
branches: [main]
jobs:
build:
runs-on: [self-hosted, ARM64, raspberry-pi]
steps:
- uses: actions/checkout@v4
- name: Show architecture
run: uname -m
- name: Build ARM64 image
run: docker build -t myapp:arm64 .
runs-on accepts a list of labels here, and GitHub schedules the job on any runner that has all of them — that’s how you target the Pi specifically instead of any other self-hosted box you might add later (e.g. an x86_64 mini PC labelled differently).
Security: read this before you register anything
GitHub’s own documentation is blunt about this: self-hosted runners should almost never be used on public repositories. Anyone can open a pull request against a public repo, and a malicious PR can modify workflow YAML — including the runs-on field — to schedule arbitrary code on your runner. That runner has network access to your LAN, your secrets, and whatever else lives on the machine. A Pi sitting on your home network is a much softer target than a throwaway GitHub-hosted VM.
- Only register self-hosted runners against private repositories, or public repos where you tightly control who can open pull requests.
- If you must use one on a repo with external contributors, require approval for first-time contributors’ workflow runs (Settings > Actions > General > Fork pull request workflows).
- Never store production secrets accessible to workflows that can be triggered by forks.
- Consider putting the Pi on its own VLAN or at least firewalling it away from anything sensitive on your home network — treat it as a semi-trusted box, not a fully trusted one.
- Keep the runner updated; GitHub ships security fixes to the runner binary itself, not just the Actions platform.
For a homelab attached to private repos this is a very reasonable risk to accept. For anything public-facing, don’t.