• Skip to primary navigation
  • Skip to main content
  • Skip to footer

Miguel Ángel Antolín Bermúdez

Personal portfolio & Blog

  • Home
  • Blog & posts
You are here: Home / DevOps / Self-Hosted GitHub Actions Runner on a Raspberry Pi

DevOps, Engineering, how to, Raspberry Pi, Self-hosting / 25th August 2026

Self-Hosted GitHub Actions Runner on a Raspberry Pi

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 print aarch64.
  • 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) sudo access 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 default self-hosted, Linux, ARM64 labels GitHub assigns automatically based on the detected OS/arch, but it’s worth being explicit so your workflow’s runs-on is 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.

Filed Under: DevOps, Engineering, how to, Raspberry Pi, Self-hosting Tagged With: arm64, ci-cd, devops, github-actions, Raspberry Pi, self-hosted-runner

Footer

Find me at

  • GitHub
  • LinkedIn

Recent Posts

  • Self-Hosted GitHub Actions Runner on a Raspberry Pi
  • k3s: Kubernetes on a Raspberry Pi Cluster
  • Monitoring Your Raspberry Pi Homelab with Prometheus and Grafana
  • Git for solo developers: the workflow that actually works
  • Self-hosting Vaultwarden on a VPS: your own Bitwarden server

© 2026 · Made with ❤️ in Seville