Your builds shouldn't cost 10x more than everything else
GitHub Actions bills build minutes on shared 2-vCore runners. A team compiling Rust, building Docker images or running Playwright test suites quickly exceeds €500/month — for mediocre performance. A self-hosted runner on a Quantum (8 EPYC Genoa vCores, 32 GB of RAM, NVMe Gen4) does the same work three times faster, for a fixed monthly cost.
The math is quick: if your pipelines consume more than 2,000 build minutes per month, self-hosting pays off. Below that, stay on managed runners.
GitHub Actions runner in five minutes
mkdir actions-runner && cd actions-runner
curl -o runner.tar.gz -L https://github.com/actions/runner/releases/download/v2.321.0/actions-runner-linux-x64-2.321.0.tar.gz
tar xzf runner.tar.gz
./config.sh --url https://github.com/your-org --token YOUR_TOKEN
sudo ./svc.sh install && sudo ./svc.sh startThe runner registers as a systemd service and picks up jobs from the next push. For Docker builds, install Docker on the host and mount the socket — or better, use rootless mode.
GitLab Runner: same logic
docker run -d --name gitlab-runner --restart always \
-v /srv/gitlab-runner/config:/etc/gitlab-runner \
-v /var/run/docker.sock:/var/run/docker.sock \
gitlab/gitlab-runner:latestRegister it with gitlab-runner register and your project token. The docker executor mode isolates each job in a disposable container: exactly what GitLab.com does, except the disk behind it is 1M IOPS NVMe Gen4 — docker pull of a 2 GB image takes 3 seconds.
Caching: where everything is won
The difference between a 12-minute pipeline and a 3-minute pipeline is rarely CPU: it's cache. Persistent Docker layer cache, dependency cache (npm, cargo, pip) on a dedicated volume, and a local pull-through registry mirror. On a shared runner, every job starts from a blank disk; on your VPS, everything persists between builds.
Security: a runner is a backdoor if you forget it
A runner executes arbitrary code from your repositories — including pull requests from forks if you allow it. Three non-negotiable rules: runner dedicated to private repos only (never a shared runner on a public repo), secrets injected via protected CI variables rather than files, and UFW exposing only SSH. Our Ubuntu hardening guide covers the rest.
Sizing for the team
One Quantum absorbs 3-4 parallel medium builds. A 15-developer team pushing 50 times a day fits comfortably. Beyond that, add a second Quantum rather than a bigger server: two isolated runners beat one large single point of failure.