{"id":54006,"date":"2025-11-03T08:30:59","date_gmt":"2025-11-03T08:30:59","guid":{"rendered":"https:\/\/www.devopsschool.com\/blog\/?p=54006"},"modified":"2025-11-03T08:30:59","modified_gmt":"2025-11-03T08:30:59","slug":"how-to-install-and-configure-github-self-hosted-runners-using-ansible","status":"publish","type":"post","link":"https:\/\/www.devopsschool.com\/blog\/how-to-install-and-configure-github-self-hosted-runners-using-ansible\/","title":{"rendered":"How to Install and Configure GitHub Self-Hosted Runners Using Ansible"},"content":{"rendered":"\n<p><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\"><\/h1>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\ude80 <strong>1. Introduction<\/strong><\/h2>\n\n\n\n<p><strong>GitHub Actions self-hosted runners<\/strong> let you run CI\/CD jobs on your own machines instead of GitHub-hosted ones.<br>Using <strong>Ansible<\/strong>, you can automate the installation, registration, and management of runners across multiple servers\u2014securely and repeatably.<\/p>\n\n\n\n<p>This tutorial covers:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>What GitHub Runners are<\/li>\n\n\n\n<li>Why use Ansible to manage them<\/li>\n\n\n\n<li>Installation prerequisites<\/li>\n\n\n\n<li>Step-by-step setup (two methods)<\/li>\n\n\n\n<li>Real Ansible playbooks (systemd and Docker)<\/li>\n\n\n\n<li>Troubleshooting and best practices<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83e\udde0 <strong>2. What is a GitHub Runner?<\/strong><\/h2>\n\n\n\n<p>A <strong>GitHub Actions Runner<\/strong> is an agent process that runs your CI\/CD jobs from GitHub.<br>When a workflow is triggered, GitHub dispatches jobs to available runners.<\/p>\n\n\n\n<p>You can host runners:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>GitHub-Hosted:<\/strong> Managed by GitHub (default)<\/li>\n\n\n\n<li><strong>Self-Hosted:<\/strong> You install and manage them (ideal for custom hardware, private environments, or cost control)<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\u2699\ufe0f <strong>3. Why Use Ansible for Runners<\/strong><\/h2>\n\n\n\n<p>Manually registering and configuring self-hosted runners is tedious and error-prone.<br>Ansible simplifies this by:<\/p>\n\n\n\n<p>\u2705 Automating installation &amp; upgrades<br>\u2705 Managing multiple runners or servers<br>\u2705 Handling registration tokens &amp; environment variables securely<br>\u2705 Supporting repeatable, idempotent deployments<br>\u2705 Integrating easily with CI\/CD pipelines or infrastructure as code<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83e\udde9 <strong>4. Architecture Options<\/strong><\/h2>\n\n\n\n<p>You have two main ways to deploy runners:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Type<\/th><th>Managed By<\/th><th>Suitable For<\/th><th>Isolation<\/th><th>Example Role<\/th><\/tr><\/thead><tbody><tr><td><strong>Systemd (Host)<\/strong><\/td><td>OS service<\/td><td>Long-lived runners<\/td><td>Shared<\/td><td><code>MonolithProjects<\/code><\/td><\/tr><tr><td><strong>Containerized (Docker)<\/strong><\/td><td>Docker containers<\/td><td>Ephemeral\/Scalable runners<\/td><td>High<\/td><td><code>compscidr<\/code><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83e\uddf0 <strong>5. Prerequisites<\/strong><\/h2>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>GitHub Personal Access Token<\/strong><\/h3>\n\n\n\n<p>Create a token (PAT) with the <code>repo<\/code> and <code>admin:org<\/code> or <code>workflow<\/code> scopes depending on whether it\u2019s repo- or org-level.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>GitHub \u2192 Settings \u2192 Developer Settings \u2192 Personal Access Tokens \u2192 \u201cFine-grained tokens\u201d or \u201cClassic\u201d \u2192 Enable scopes:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>repo<\/code><\/li>\n\n\n\n<li><code>admin:org<\/code><\/li>\n\n\n\n<li><code>workflow<\/code><\/li>\n<\/ul>\n<\/blockquote>\n\n\n\n<p>Export it on your control node:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">export<\/span> GH_PAT=<span class=\"hljs-string\">\"ghp_xxxxxxxxxxxxxxxxxxxxx\"<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Ansible Installed<\/strong><\/h3>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">pip install ansible\n<\/code><\/span><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Target Hosts<\/strong><\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>RHEL \/ CentOS \/ Ubuntu with Python installed<\/li>\n\n\n\n<li>SSH access from control node<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83e\uddf1 <strong>6. Option A: Systemd Runners (Recommended for VM\/Server)<\/strong><\/h2>\n\n\n\n<p>We\u2019ll use <a href=\"https:\/\/github.com\/MonolithProjects\/ansible-github_actions_runner\" target=\"_blank\" rel=\"noopener\"><code>MonolithProjects\/ansible-github_actions_runner<\/code><\/a>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Step 1: Install the Role<\/strong><\/h3>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">ansible-galaxy role install git+https:<span class=\"hljs-comment\">\/\/github.com\/MonolithProjects\/ansible-github_actions_runner.git,1.21.1<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>or use <code>requirements.yml<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">---\nroles:\n  - name: monolithprojects.github_actions_runner\n    <span class=\"hljs-attr\">src<\/span>: https:<span class=\"hljs-comment\">\/\/github.com\/MonolithProjects\/ansible-github_actions_runner<\/span>\n    version: <span class=\"hljs-string\">\"1.21.1\"<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>then run:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">ansible-galaxy<\/span> <span class=\"hljs-selector-tag\">install<\/span> <span class=\"hljs-selector-tag\">-r<\/span> <span class=\"hljs-selector-tag\">requirements<\/span><span class=\"hljs-selector-class\">.yml<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Step 2: Create Your Inventory<\/strong><\/h3>\n\n\n\n<p><code>inventory.ini<\/code><\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">&#91;github_runners]\nrunner1 ansible_host=192.168.56.10 ansible_user=ec2-user\n<\/code><\/span><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Step 3: Write the Playbook<\/strong><\/h3>\n\n\n\n<p><code>install_runner.yml<\/code><\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">- name: Install GitHub Runner via Ansible (Systemd)\n  hosts: github_runners\n  become: <span class=\"hljs-keyword\">true<\/span>\n\n  vars:\n    github_account: <span class=\"hljs-string\">\"my-org-or-user\"<\/span>\n    github_repo: <span class=\"hljs-string\">\"my-repo\"<\/span>          <span class=\"hljs-comment\"># optional if org-level<\/span>\n    runner_user: <span class=\"hljs-string\">\"runner\"<\/span>\n    runner_labels:\n      - linux\n      - ansible\n    runner_state: present\n    github_token: <span class=\"hljs-string\">\"{{ lookup('env','GH_PAT') }}\"<\/span>  <span class=\"hljs-comment\"># use env variable<\/span>\n\n  roles:\n    - monolithprojects.github_actions_runner\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Step 4: Run the Playbook<\/strong><\/h3>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">ansible-playbook<\/span> <span class=\"hljs-selector-tag\">-i<\/span> <span class=\"hljs-selector-tag\">inventory<\/span><span class=\"hljs-selector-class\">.ini<\/span> <span class=\"hljs-selector-tag\">install_runner<\/span><span class=\"hljs-selector-class\">.yml<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>\u2705 This installs, registers, and enables the runner as a <strong>systemd service<\/strong>.<br>Check the service:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">sudo<\/span> <span class=\"hljs-selector-tag\">systemctl<\/span> <span class=\"hljs-selector-tag\">status<\/span> <span class=\"hljs-selector-tag\">actions<\/span><span class=\"hljs-selector-class\">.runner<\/span>*\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Step 5: Verify on GitHub<\/strong><\/h3>\n\n\n\n<p>Go to<br><strong>Repo \u2192 Settings \u2192 Actions \u2192 Runners<\/strong><br>\u2192 You\u2019ll see the new runner with your label and status = <em>online<\/em>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\udc33 <strong>7. Option B: Dockerized Runners (for High Density &amp; Isolation)<\/strong><\/h2>\n\n\n\n<p>We\u2019ll use <a href=\"https:\/\/github.com\/compscidr\/ansible-github-runner\" target=\"_blank\" rel=\"noopener\"><code>compscidr\/ansible-github-runner<\/code><\/a>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Step 1: Install Role<\/strong><\/h3>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">ansible-galaxy<\/span> <span class=\"hljs-selector-tag\">role<\/span> <span class=\"hljs-selector-tag\">install<\/span> <span class=\"hljs-selector-tag\">compscidr<\/span><span class=\"hljs-selector-class\">.github_runner<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Make sure Docker is available on your hosts:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">ansible -m apt -a <span class=\"hljs-string\">\"name=docker.io state=present\"<\/span> all\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Step 2: Create Inventory<\/strong><\/h3>\n\n\n\n<p><code>inventory.ini<\/code><\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">&#91;docker_runner_hosts]\nrunner2 ansible_host=192.168.56.11 ansible_user=ubuntu\n<\/code><\/span><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Step 3: Playbook<\/strong><\/h3>\n\n\n\n<p><code>docker_runner.yml<\/code><\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">- name: Install Dockerized GitHub Runner\n  hosts: docker_runner_hosts\n  become: <span class=\"hljs-keyword\">true<\/span>\n  vars:\n    github_owner: <span class=\"hljs-string\">\"my-org-or-user\"<\/span>\n    github_repo: <span class=\"hljs-string\">\"my-repo\"<\/span>\n    github_token: <span class=\"hljs-string\">\"{{ lookup('env','GH_PAT') }}\"<\/span>\n    github_runner_count: <span class=\"hljs-number\">3<\/span>                 <span class=\"hljs-comment\"># creates 3 runners (containers)<\/span>\n    github_runner_labels:\n      - docker\n      - <span class=\"hljs-keyword\">self<\/span>-hosted\n    github_runner_image: <span class=\"hljs-string\">\"ghcr.io\/myoung34\/github-runner:latest\"<\/span>\n    github_runner_workdir: <span class=\"hljs-string\">\"\/tmp\/runner\"<\/span>\n  roles:\n    - compscidr.github_runner\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Step 4: Run Playbook<\/strong><\/h3>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">ansible-playbook<\/span> <span class=\"hljs-selector-tag\">-i<\/span> <span class=\"hljs-selector-tag\">inventory<\/span><span class=\"hljs-selector-class\">.ini<\/span> <span class=\"hljs-selector-tag\">docker_runner<\/span><span class=\"hljs-selector-class\">.yml<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This will:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Pull the container image<\/li>\n\n\n\n<li>Register each container runner with GitHub<\/li>\n\n\n\n<li>Start them automatically via Docker<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Step 5: Validate<\/strong><\/h3>\n\n\n\n<p>List containers:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">docker ps\n<\/code><\/span><\/pre>\n\n\n<p>Check runners online at<br><strong>GitHub \u2192 Settings \u2192 Actions \u2192 Runners<\/strong><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83e\uddf0 <strong>8. Option C: Minimal Role (rolehippie)<\/strong><\/h2>\n\n\n\n<p>Use if you want lightweight manual control.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">ansible-galaxy<\/span> <span class=\"hljs-selector-tag\">role<\/span> <span class=\"hljs-selector-tag\">install<\/span> <span class=\"hljs-selector-tag\">rolehippie<\/span><span class=\"hljs-selector-class\">.github_runner<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p><strong>Playbook<\/strong><\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">- name: Minimal GitHub Runner Setup\n  <span class=\"hljs-attr\">hosts<\/span>: all\n  <span class=\"hljs-attr\">become<\/span>: <span class=\"hljs-literal\">true<\/span>\n  <span class=\"hljs-attr\">vars<\/span>:\n    runner_workdir: <span class=\"hljs-regexp\">\/opt\/<\/span>actions-runner\n    <span class=\"hljs-attr\">runner_labels<\/span>: &#91;<span class=\"hljs-string\">\"linux\"<\/span>,<span class=\"hljs-string\">\"self-hosted\"<\/span>]\n    <span class=\"hljs-attr\">runner_repo<\/span>: <span class=\"hljs-string\">\"my-org-or-user\/my-repo\"<\/span>\n    <span class=\"hljs-attr\">runner_token<\/span>: <span class=\"hljs-string\">\"{{ lookup('env','RUNNER_REG_TOKEN') }}\"<\/span>\n  <span class=\"hljs-attr\">roles<\/span>:\n    - rolehippie.github_runner\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>Note: You must manually fetch a <strong>runner registration token<\/strong> using the GitHub REST API or UI before running this playbook.<\/p>\n<\/blockquote>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83e\uddea <strong>9. Validating the Runner<\/strong><\/h2>\n\n\n\n<p>Once playbooks finish:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Log in to your GitHub repo\/org<br>\u2192 <strong>Settings \u2192 Actions \u2192 Runners<\/strong><\/li>\n\n\n\n<li>Verify status shows <strong>\u201cOnline\u201d<\/strong><\/li>\n\n\n\n<li>Run a test workflow using your label:<\/li>\n<\/ol>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-14\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">name: Test Runner\non: &#91;push]\njobs:\n  test:\n    runs-on: &#91;<span class=\"hljs-keyword\">self<\/span>-hosted, ansible]\n    steps:\n      - run: <span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"Hello from self-hosted runner!\"<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\u26a1 <strong>10. Updating or Removing a Runner<\/strong><\/h2>\n\n\n\n<p>To remove:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-15\" data-shcb-language-name=\"HTTP\" data-shcb-language-slug=\"http\"><span><code class=\"hljs language-http\"><span class=\"hljs-attribute\">runner_state<\/span>: absent\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-15\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTTP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">http<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Then re-run your playbook \u2014 it unregisters and stops the service.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83e\udded <strong>11. Troubleshooting<\/strong><\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Problem<\/th><th>Possible Fix<\/th><\/tr><\/thead><tbody><tr><td>Runner not appearing in GitHub<\/td><td>Check token validity and scopes<\/td><\/tr><tr><td>Runner shows offline<\/td><td>Check network and systemd logs<\/td><\/tr><tr><td>Docker containers exit quickly<\/td><td>Verify PAT and environment variables<\/td><\/tr><tr><td>\u201cPermission denied\u201d<\/td><td>Ensure <code>become: true<\/code> or correct user privileges<\/td><\/tr><tr><td>Multiple runners overwrite each other<\/td><td>Use unique names or containerized approach<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\udd10 <strong>12. Best Practices<\/strong><\/h2>\n\n\n\n<p>\u2705 Use <strong>Personal Access Tokens (PAT)<\/strong>, not registration tokens in playbooks<br>\u2705 Store tokens in Ansible Vault (<code>ansible-vault encrypt_string<\/code>)<br>\u2705 Use <strong>labels<\/strong> to route jobs cleanly<br>\u2705 Pin runner versions for reproducibility<br>\u2705 Use Dockerized runners for ephemeral workloads<br>\u2705 Rotate tokens periodically<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83c\udfc1 <strong>13. Summary<\/strong><\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Deployment Type<\/th><th>Best For<\/th><th>Example Role<\/th><\/tr><\/thead><tbody><tr><td><strong>Systemd Runner<\/strong><\/td><td>Long-lived VM runners<\/td><td>MonolithProjects<\/td><\/tr><tr><td><strong>Dockerized Runner<\/strong><\/td><td>Scalable, short-lived runners<\/td><td>compscidr<\/td><\/tr><tr><td><strong>Minimal DIY<\/strong><\/td><td>Custom\/light setups<\/td><td>rolehippie<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\udcd8 <strong>14. References<\/strong><\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\ud83d\udd17 <a href=\"https:\/\/github.com\/MonolithProjects\/ansible-github_actions_runner\" target=\"_blank\" rel=\"noopener\">MonolithProjects GitHub Runner Role<\/a><\/li>\n\n\n\n<li>\ud83d\udd17 <a href=\"https:\/\/github.com\/compscidr\/ansible-github-runner\" target=\"_blank\" rel=\"noopener\">compscidr GitHub Runner Role<\/a><\/li>\n\n\n\n<li>\ud83d\udd17 <a href=\"https:\/\/github.com\/rolehippie\/github-runner\" target=\"_blank\" rel=\"noopener\">rolehippie GitHub Runner Role<\/a><\/li>\n\n\n\n<li>\ud83d\udd17 <a href=\"https:\/\/docs.github.com\/en\/actions\/hosting-your-own-runners\/about-self-hosted-runners\" target=\"_blank\" rel=\"noopener\">GitHub Actions Self-Hosted Runner Docs<\/a><\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p>\u2705 <strong>In summary:<\/strong><br>If you\u2019re new to self-hosted runners\u2014start with <strong>MonolithProjects<\/strong> (simple and reliable).<br>If you need scale and isolation\u2014use <strong>compscidr<\/strong> (Dockerized).<br>For ultimate control and minimal overhead\u2014<strong>rolehippie<\/strong> is fine for tinkering.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>\ud83d\ude80 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&#8230; <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"_joinchat":[],"footnotes":""},"categories":[11138],"tags":[],"class_list":["post-54006","post","type-post","status-publish","format-standard","hentry","category-best-tools"],"_links":{"self":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/54006","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/comments?post=54006"}],"version-history":[{"count":1,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/54006\/revisions"}],"predecessor-version":[{"id":54007,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/54006\/revisions\/54007"}],"wp:attachment":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/media?parent=54006"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/categories?post=54006"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/tags?post=54006"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}