🚀 1. Introduction
GitHub Actions self-hosted runners let you run CI/CD jobs on your own machines instead of GitHub-hosted ones.
Using Ansible, you can automate the installation, registration, and management of runners across multiple servers—securely and repeatably.
This tutorial covers:
- What GitHub Runners are
- Why use Ansible to manage them
- Installation prerequisites
- Step-by-step setup (two methods)
- Real Ansible playbooks (systemd and Docker)
- Troubleshooting and best practices
🧠 2. What is a GitHub Runner?
A GitHub Actions Runner is an agent process that runs your CI/CD jobs from GitHub.
When a workflow is triggered, GitHub dispatches jobs to available runners.
You can host runners:
- GitHub-Hosted: Managed by GitHub (default)
- Self-Hosted: You install and manage them (ideal for custom hardware, private environments, or cost control)
⚙️ 3. Why Use Ansible for Runners
Manually registering and configuring self-hosted runners is tedious and error-prone.
Ansible simplifies this by:
✅ Automating installation & upgrades
✅ Managing multiple runners or servers
✅ Handling registration tokens & environment variables securely
✅ Supporting repeatable, idempotent deployments
✅ Integrating easily with CI/CD pipelines or infrastructure as code
🧩 4. Architecture Options
You have two main ways to deploy runners:
| Type | Managed By | Suitable For | Isolation | Example Role |
|---|---|---|---|---|
| Systemd (Host) | OS service | Long-lived runners | Shared | MonolithProjects |
| Containerized (Docker) | Docker containers | Ephemeral/Scalable runners | High | compscidr |
🧰 5. Prerequisites
GitHub Personal Access Token
Create a token (PAT) with the repo and admin:org or workflow scopes depending on whether it’s repo- or org-level.
GitHub → Settings → Developer Settings → Personal Access Tokens → “Fine-grained tokens” or “Classic” → Enable scopes:
repoadmin:orgworkflow
Export it on your control node:
export GH_PAT="ghp_xxxxxxxxxxxxxxxxxxxxx"
Code language: JavaScript (javascript)
Ansible Installed
pip install ansible
Target Hosts
- RHEL / CentOS / Ubuntu with Python installed
- SSH access from control node
🧱 6. Option A: Systemd Runners (Recommended for VM/Server)
We’ll use MonolithProjects/ansible-github_actions_runner.
Step 1: Install the Role
ansible-galaxy role install git+https://github.com/MonolithProjects/ansible-github_actions_runner.git,1.21.1
Code language: JavaScript (javascript)
or use requirements.yml:
---
roles:
- name: monolithprojects.github_actions_runner
src: https://github.com/MonolithProjects/ansible-github_actions_runner
version: "1.21.1"
Code language: JavaScript (javascript)
then run:
ansible-galaxy install -r requirements.yml
Code language: CSS (css)
Step 2: Create Your Inventory
inventory.ini
[github_runners]
runner1 ansible_host=192.168.56.10 ansible_user=ec2-user
Step 3: Write the Playbook
install_runner.yml
- name: Install GitHub Runner via Ansible (Systemd)
hosts: github_runners
become: true
vars:
github_account: "my-org-or-user"
github_repo: "my-repo" # optional if org-level
runner_user: "runner"
runner_labels:
- linux
- ansible
runner_state: present
github_token: "{{ lookup('env','GH_PAT') }}" # use env variable
roles:
- monolithprojects.github_actions_runner
Code language: PHP (php)
Step 4: Run the Playbook
ansible-playbook -i inventory.ini install_runner.yml
Code language: CSS (css)
✅ This installs, registers, and enables the runner as a systemd service.
Check the service:
sudo systemctl status actions.runner*
Code language: CSS (css)
Step 5: Verify on GitHub
Go to
Repo → Settings → Actions → Runners
→ You’ll see the new runner with your label and status = online.
🐳 7. Option B: Dockerized Runners (for High Density & Isolation)
We’ll use compscidr/ansible-github-runner.
Step 1: Install Role
ansible-galaxy role install compscidr.github_runner
Code language: CSS (css)
Make sure Docker is available on your hosts:
ansible -m apt -a "name=docker.io state=present" all
Code language: JavaScript (javascript)
Step 2: Create Inventory
inventory.ini
[docker_runner_hosts]
runner2 ansible_host=192.168.56.11 ansible_user=ubuntu
Step 3: Playbook
docker_runner.yml
- name: Install Dockerized GitHub Runner
hosts: docker_runner_hosts
become: true
vars:
github_owner: "my-org-or-user"
github_repo: "my-repo"
github_token: "{{ lookup('env','GH_PAT') }}"
github_runner_count: 3 # creates 3 runners (containers)
github_runner_labels:
- docker
- self-hosted
github_runner_image: "ghcr.io/myoung34/github-runner:latest"
github_runner_workdir: "/tmp/runner"
roles:
- compscidr.github_runner
Code language: PHP (php)
Step 4: Run Playbook
ansible-playbook -i inventory.ini docker_runner.yml
Code language: CSS (css)
This will:
- Pull the container image
- Register each container runner with GitHub
- Start them automatically via Docker
Step 5: Validate
List containers:
docker ps
Check runners online at
GitHub → Settings → Actions → Runners
🧰 8. Option C: Minimal Role (rolehippie)
Use if you want lightweight manual control.
ansible-galaxy role install rolehippie.github_runner
Code language: CSS (css)
Playbook
- name: Minimal GitHub Runner Setup
hosts: all
become: true
vars:
runner_workdir: /opt/actions-runner
runner_labels: ["linux","self-hosted"]
runner_repo: "my-org-or-user/my-repo"
runner_token: "{{ lookup('env','RUNNER_REG_TOKEN') }}"
roles:
- rolehippie.github_runner
Code language: JavaScript (javascript)
Note: You must manually fetch a runner registration token using the GitHub REST API or UI before running this playbook.
🧪 9. Validating the Runner
Once playbooks finish:
- Log in to your GitHub repo/org
→ Settings → Actions → Runners - Verify status shows “Online”
- Run a test workflow using your label:
name: Test Runner
on: [push]
jobs:
test:
runs-on: [self-hosted, ansible]
steps:
- run: echo "Hello from self-hosted runner!"
Code language: PHP (php)
⚡ 10. Updating or Removing a Runner
To remove:
runner_state: absent
Code language: HTTP (http)
Then re-run your playbook — it unregisters and stops the service.
🧭 11. Troubleshooting
| Problem | Possible Fix |
|---|---|
| Runner not appearing in GitHub | Check token validity and scopes |
| Runner shows offline | Check network and systemd logs |
| Docker containers exit quickly | Verify PAT and environment variables |
| “Permission denied” | Ensure become: true or correct user privileges |
| Multiple runners overwrite each other | Use unique names or containerized approach |
🔐 12. Best Practices
✅ Use Personal Access Tokens (PAT), not registration tokens in playbooks
✅ Store tokens in Ansible Vault (ansible-vault encrypt_string)
✅ Use labels to route jobs cleanly
✅ Pin runner versions for reproducibility
✅ Use Dockerized runners for ephemeral workloads
✅ Rotate tokens periodically
🏁 13. Summary
| Deployment Type | Best For | Example Role |
|---|---|---|
| Systemd Runner | Long-lived VM runners | MonolithProjects |
| Dockerized Runner | Scalable, short-lived runners | compscidr |
| Minimal DIY | Custom/light setups | rolehippie |
📘 14. References
- 🔗 MonolithProjects GitHub Runner Role
- 🔗 compscidr GitHub Runner Role
- 🔗 rolehippie GitHub Runner Role
- 🔗 GitHub Actions Self-Hosted Runner Docs
✅ In summary:
If you’re new to self-hosted runners—start with MonolithProjects (simple and reliable).
If you need scale and isolation—use compscidr (Dockerized).
For ultimate control and minimal overhead—rolehippie is fine for tinkering.
I’m a DevOps/SRE/DevSecOps/Cloud Expert passionate about sharing knowledge and experiences. I have worked at Cotocus. I share tech blog at DevOps School, travel stories at Holiday Landmark, stock market tips at Stocks Mantra, health and fitness guidance at My Medic Plus, product reviews at TrueReviewNow , and SEO strategies at Wizbrand.
Do you want to learn Quantum Computing?
Please find my social handles as below;
Rajesh Kumar Personal Website
Rajesh Kumar at YOUTUBE
Rajesh Kumar at INSTAGRAM
Rajesh Kumar at X
Rajesh Kumar at FACEBOOK
Rajesh Kumar at LINKEDIN
Rajesh Kumar at WIZBRAND
This article delivers a clear and practical explanation of how to use Ansible to set up GitHub self-hosted runners — automating what would otherwise be a tedious, manual process. I like how it outlines each step and the configuration required, making it much easier for DevOps teams to replicate across multiple hosts. The guide makes a strong case for why using infrastructure-as-code tools like Ansible boosts consistency, repeatability, and reduces the risk of human error in CI/CD setups. For anyone managing build automation or custom deployment pipelines, this post is a valuable resource that can save time and improve reliability.