š 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