← How-Tos
raspberry-pi Jul 25, 2026 ◑ 15 views ◯ 6 min read

Self-Hosted CI/CD Runner on a Raspberry Pi: GitHub Actions and Gitea Actions

raspberry pici/cdgithub actionsgiteaself hosted runnerdockerautomationdevops

If you write any amount of code for your projects — firmware for an ESP32 project, a Klipper macro repo, a personal website, scripts that manage your homelab — a CI/CD runner gives you automatic testing, linting, and builds on every commit without paying for cloud minutes or trusting a third party with build secrets. A Raspberry Pi is a genuinely good fit for this: it's low power enough to leave running 24/7, quiet enough to sit on a shelf, and more than capable of running lint checks, unit tests, and small builds for most personal-scale projects. This guide covers wiring up a self-hosted runner for both GitHub Actions and Gitea Actions (if you're also self-hosting your git server on a Pi, per our Raspberry Pi home server guide).

Why Self-Host a Runner

The tradeoff: you're responsible for keeping the runner patched and isolated, since a CI runner executes arbitrary code from your repos (and, if misconfigured, potentially from pull requests submitted by others) — see the security section below before exposing this to anything beyond your own private repos.

Hardware Recommendation

A Raspberry Pi 4 or 5 with at least 4GB RAM and a fast USB SSD (not a microSD card — CI workloads do enough small file I/O that SD cards become both a bottleneck and a wear-out risk) handles most personal-project CI comfortably: linting, unit tests, small Docker builds, and static site builds. Heavier workloads (compiling large C++ projects, building multi-arch Docker images) will be noticeably slower than a cloud runner's dedicated hardware, but for the kind of firmware and script-level projects typical of a maker's repos, a Pi keeps up fine.

Setting Up a GitHub Actions Self-Hosted Runner

GitHub's official runner binary runs natively on Raspberry Pi OS (64-bit is required — confirm you're running the 64-bit image before starting):

# On the Pi, create a runner directory mkdir actions-runner && cd actions-runner # Download the ARM64 runner package (get the current version/URL from # your repo's Settings -> Actions -> Runners -> New self-hosted runner page) curl -o actions-runner.tar.gz -L https://github.com/actions/runner/releases/download/vX.X.X/actions-runner-linux-arm64-X.X.X.tar.gz tar xzf actions-runner.tar.gz # Configure, using the token shown on the same GitHub settings page ./config.sh --url https://github.com/YOUR_USER/YOUR_REPO --token YOUR_TOKEN # Install and start as a systemd service so it survives reboots sudo ./svc.sh install sudo ./svc.sh start

Once registered, the runner shows up under the repo's (or organization's) Settings > Actions > Runners page, and any workflow file specifying runs-on: self-hosted will queue on your Pi instead of GitHub's cloud runners. Add custom labels during config.sh (e.g. --labels rpi,arm64) so workflows can target this specific runner deliberately with runs-on: [self-hosted, rpi] rather than any self-hosted runner you might add later.

Setting Up a Gitea Actions Runner

If you're running Gitea (or Forgejo) on your network, Gitea Actions uses a compatible workflow syntax to GitHub Actions and its own runner binary, act_runner:

# Download act_runner (ARM64 build) from Gitea's releases page wget https://gitea.com/gitea/act_runner/releases/download/vX.X.X/act_runner-X.X.X-linux-arm64 chmod +x act_runner-X.X.X-linux-arm64 sudo mv act_runner-X.X.X-linux-arm64 /usr/local/bin/act_runner # Register against your Gitea instance (token from Gitea's admin/repo Actions settings) act_runner register --instance https://your-gitea-url --token YOUR_TOKEN # Run it (wrap in a systemd service for persistence) act_runner daemon

act_runner supports both a Docker-backed execution mode (isolates each job in a container, recommended) and a native/host execution mode. On a Pi, Docker mode adds some overhead but is worth the isolation, especially if more than one person or repo will trigger jobs on the runner.

Running Jobs in Docker for Isolation

Whichever platform you use, running CI jobs inside Docker containers rather than directly on the Pi's host OS is the safer default — it keeps a broken or malicious build script from touching the rest of the Pi's filesystem or other services you're running on it. Install Docker on the Pi (the official convenience script works fine on Raspberry Pi OS 64-bit) and, for GitHub Actions specifically, note that the vanilla self-hosted runner doesn't auto-containerize jobs the way GitHub-hosted runners do — jobs run directly on the host unless your workflow explicitly uses a container: directive, so add one for anything you don't fully trust.

Multi-Architecture Considerations

A Pi runner is ARM64 (aarch64), and if your project also needs to build or test for x86_64, you'll need either a second runner on x86 hardware, GitHub's hosted runners for that architecture in the same workflow (mixing self-hosted and hosted runners across jobs is fully supported), or Docker Buildx with QEMU emulation for cross-building images — emulated cross-builds work but are noticeably slower than native. For firmware projects (ESP32, Arduino, Flipper Zero apps) this usually isn't an issue since those toolchains cross-compile from ARM64 just as well as x86_64.

Security Notes

What This Is Good For

Linting and unit tests on every commit, building and validating Klipper/firmware configs before flashing, building a static site (like a personal maker log) and deploying it on push, running scheduled jobs (nightly backups, link checkers, dependency update checks) — all of it running for free, on hardware you already understand, without a monthly CI minutes bill.