Host setup (manual steps)#
gh sr automates runner installation and lifecycle over SSH, but some host preparation is still manual. After you edit config, run gh sr doctor from your laptop to verify config paths, GitHub API access, SSH connectivity, and per-host tools (Docker vs native). By default the command exits with a non-zero status only when a check is FAIL; use gh sr doctor --strict if you also want WARN lines to fail (for example in CI).
Linux SSH user and privileges#
gh sr setup and gh sr update run remote commands as the SSH user in hosts.*.addr. On Linux, when a step needs root (package installs, GitHub’s installdependencies.sh for native runners, or Docker’s install script when Docker is missing), gh sr uses sudo -n only — non-interactive sudo that never prompts for a password.
Why sudo -n: Remote automation uses SSH sessions without a PTY (no interactive terminal), the same way ssh runs a remote command by default. Interactive sudo would try to read a password from a TTY and fails with errors such as “a terminal is required to read the password”. So gh sr requires SSH as root or passwordless sudo (sudo -n true must succeed on the host) for those install steps.
If privilege checks fail, remote stderr may include:
gh sr: remote Linux commands need root SSH or passwordless sudo (non-interactive); SSH has no TTY for sudo passwords. Use NOPASSWD, connect as root, or install software manually. Run: gh sr doctorRun gh sr doctor on your laptop to check Linux hosts: it prints “linux: non-root user has passwordless sudo” when sudo -n works, or “linux: passwordless sudo not available…” when it does not. Use gh sr doctor --strict if you want that warning to fail the command.
Setting up the SSH user on Linux#
- Dedicated user + SSH keys — Create a user on the runner host, authorize your public key in
~/.ssh/authorized_keys, and sethosts.*.addrtouser@host(or hostname). root@hostinhosts.*.addr— Avoids sudo entirely for gh sr’s install paths. Only use if your security policy allows SSH as root; prefer key-based auth, disable password login, and restrict network access.- Passwordless sudo (
NOPASSWD) — On the host, usevisudoor a drop-in under/etc/sudoers.d/. A broad rule such asrunner ALL=(ALL) NOPASSWD: ALLis common for a dedicated CI user. Command-scoped NOPASSWD is hard to maintain for Docker’sget.docker.comscript andinstalldependencies.shbecause they run many different commands; if you cannot grant broad NOPASSWD, useroot@hostor pre-install software so gh sr does not need those scripts. - Pre-install to limit elevation — Install Docker before
gh sr setupand add the SSH user to thedockergroup sodockerworks withoutsudo. For native mode, ensurecurlandtarare onPATHand install distro packages the runner needs in advance; that reduces how ofteninstalldependencies.shmust run with sudo.
Verify from your laptop (same user@host as in hosts.*.addr):
ssh -o BatchMode=yes user@host true
ssh -o BatchMode=yes user@host 'sudo -n true'The first command checks non-interactive SSH (see also All remote hosts). The second must exit 0 if you rely on a non-root user with sudo for automated installs; if it fails, configure NOPASSWD for that user or use root@host.
- Container mode on Linux —
runner_mode: containerandprofile: agenticrequire Docker on the host and support for--privilegedcontainers (Docker-in-Docker). Install Docker beforegh sr setupwhen possible and add the SSH user to thedockergroup sodockerworks withoutsudo. Container mode is Linux-only; configuring container or agentic runners on macOS or Windows fails at setup and doctor reports a FAIL. - Native mode — You can avoid
sudoifcurlandtarare present and OS packages the runner needs are already installed; otherwise gh sr may print warnings and the runner might be incomplete.
gh sr does not deeply verify sudoers rules; failures show up as remote command errors or warnings.
Container mode host requirements (Linux)#
runner_mode: container and profile: agentic run each instance as an outer privileged container (gh-sr-<instance>) with its own inner dockerd. The host only needs:
- Docker CLI and daemon available to the SSH user (typically via the
dockergroup) - Ability to run short-lived
docker run --privilegedcontainers (required for DinD)
gh sr builds the gh-sr/agentic-runner image locally during setup. dnsmasq, AWF, gh-aw tooling, and host.docker.internal DNS live inside the image — not on the host.
Verify from inside a running runner container:
docker exec gh-sr-<instance> docker infogh sr doctor validates container-mode runners on Linux hosts: outer Docker, --privileged support, each gh-sr-<instance> container, inner dockerd, registration, and (for profile: agentic) inner AWF/DNS/hygiene checks. On non-Linux hosts with container-mode runners configured, doctor reports a FAIL because container mode is unsupported there.
GitHub Agentic Workflows (gh-aw)#
For a complete guide to agentic workflows on self-hosted runners – architecture, DNS setup, troubleshooting, and what
profile: agenticautomates – see Agentic Workflows.
The easiest way to set up a runner for GitHub Agentic Workflows is profile: agentic:
runners:
- name: aw-runner
repo: owner/repo
host: vps-1
profile: agentic
count: 2profile: agentic always runs in container mode (privileged Docker-in-Docker): each instance gets its own inner dockerd, network namespace, MCP gateway port, and /tmp/gh-aw, and every job runs from a pristine inner state. The host only needs Docker (with privileged-container support); gh-aw, AWF, host.docker.internal DNS, the tool cache, and language runtimes are all baked into the image gh sr builds. You can also add it from the CLI:
gh sr add runner aw-runner --repo owner/repo --host vps-1 --profile agenticFor organization-level runners with runner groups, use org and group instead of repo:
runners:
- name: org-runner
org: my-org
group: my-runner-group
host: vps-1
profile: agenticFor ephemeral runners (clean-slate per job, good for security isolation):
runners:
- name: aw-ephemeral
repo: owner/repo
host: vps-1
profile: agentic
ephemeral: trueHow it works#
Because agentic runners are container-isolated, the host needs only Docker with privileged-container support. Everything else is inside the image:
host.docker.internalDNS — baked into the image (the inner default bridge gateway is pinned to10.200.0.1and inner container DNS points at a bundled dnsmasq). No host/etc/hosts, dnsmasq, ordaemon.jsonchanges are required. Do NOT maphost.docker.internalto127.0.0.1anywhere.sudo/ iptables for AWF — the runner user inside the container already has passwordless sudo; no host sudoers setup is needed for agentic.- Tool cache (
/opt/hostedtoolcache) — provided inside the image where gh-aw’s agent containers expect it. RUNNER_TEMP— set to a non-/tmppath inside the container so it never collides with/tmp/gh-aw.
gh sr doctor validates the host Docker daemon, --privileged support, and — for each instance — the container, inner dockerd, registration, inner host.docker.internal resolution, the AWF service-routing bypass, and AWF/Docker hygiene.
See the Agentic Workflows guide for the full architecture, the per-job reset model, and troubleshooting.
Removed: the former native-agentic host setup (host dnsmasq,
/etc/sudoers.dfor iptables,/opt/hostedtoolcachebind mount) and the per-instanceagentic_mcp_ports/gh-sr-mcp-<port>label scheme.profile: agentic+runner_mode: nativeis now rejected; agentic always uses container isolation.
GitHub CLI authentication (gh)#
Agentic workflows that use gh CLI commands (e.g., gh pr list, gh issue list, gh api) require GitHub authentication on the runner host. GitHub Hosted Runners have this built-in, but self-hosted runners need manual setup.
Symptoms: Workflow steps using gh fail with errors like:
gh: You are not logged into any GitHub hostsgh: To use GitHub CLI in a GitHub Actions workflow, set the GH_TOKEN environment variableBad credentialsfrom the GitHub API
Solution: Set the GH_TOKEN environment variable in the workflow, or authenticate gh on the runner host.
Option 1 — Pass GH_TOKEN in the workflow (recommended for most cases):
jobs:
agentic:
runs-on: self-hosted
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v6
- name: List PRs
run: gh pr listOption 2 — Authenticate gh on the runner host (for local gh commands outside workflow steps):
# On the runner host, authenticate gh with a Personal Access Token
gh auth login --hostname github.com --token <your-PAT>Note:
gh auth loginis interactive by default. For non-interactive setup, use:printf '<your-token>' | gh auth login --hostname github.com --insecure-stdin-token
Verify on the runner host:
gh auth statusWindows (OpenSSH and Docker)#
Windows — OpenSSH Server enabled for native runners:
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0 Start-Service sshd Set-Service -Name sshd -StartupType AutomaticSSH default shell: OpenSSH may use cmd.exe or PowerShell 7 (
pwsh) as the remote shell depending on your setup. gh sr runs Windows automation viapowershell.exeorpwsh.exewith-EncodedCommand, so it works with either default. Usewindows_ps: pwshon the host if you rely on PowerShell 7 only and do not have Windows PowerShell 5.1.
All remote hosts#
- Confirm non-interactive SSH works:
ssh -o BatchMode=yes user@host true(use the same user and host as inhosts.*.addr). - Host keys: when
~/.ssh/known_hostsexists, gh sr verifies server keys the same way as the Goknownhostspackage. Connect once with plainsshif you need to accept a new host key beforegh sr doctororgh sr setup. - Remote commands run as the SSH user from your config; that user must have permission to install or run runners as documented for each OS below.
Run gh sr doctor (optionally with --host / --repo) to confirm connectivity from your control machine.
Linux#
For the full explanation of non-interactive SSH, the gh sr: remote Linux commands need root… error, NOPASSWD, and verification commands, see Linux SSH user and privileges above.
- Container mode (
runner_mode: container/profile: agentic): Requires Docker on the host and--privilegedcontainer support. Install Docker yourself and ensure the SSH user can rundocker(for example membership in thedockergroup). If Docker is missing,gh sr setupmay invoke Docker’s install script, which requires root or passwordless sudo (sudo -n) over SSH (see the section linked above). - Native mode (default): Ensure
curlandtarare onPATH.gh sr setupmay still invoke GitHub’sinstalldependencies.shwithsudo -nwhen the SSH user is not root and passwordless sudo is available.
Run gh sr doctor after the host is prepared.
macOS#
- Native (default):
curlis usually sufficient; gh sr downloads the Actions runner over HTTPS. - Container / agentic:
runner_mode: containerandprofile: agenticare not supported on macOS hosts. Use a Linux host for agentic workflows or container-isolated runners.
Windows (runner behavior)#
- Install and enable OpenSSH Server (see the PowerShell snippet under Windows (OpenSSH and Docker)).
- Container / agentic: Not supported on Windows hosts. Use a Linux host for
profile: agenticorrunner_mode: container. - Native mode: After
gh sr setup, rungh sr upto start the listener. Registration logs under_diagshow configure finishing with exit code 0; that is normal and does not mean the runner is listening. For the run phase, check%USERPROFILE%\.gh-sr\runners\<instance>\runner.log. Over SSH,Start-Processlisteners are killed when the session disconnects; gh sr starts the listener withWin32_Process.Create(CIM) so it keeps running aftergh srexits. If the runner shows stopped withrunner registration has been deleted from the server, GitHub auto-pruned the stale registration.gh sr updetects this automatically, re-registers, and retries. You can also fix it manually withgh sr update <runner>(which removes, re-configures, and starts in one step). Ifgh sr statusshows stopped immediately aftergh sr upfor other reasons, checkrunner.logfor error details. As a fallback you can runrun.cmdfrom an interactive session (RDP), use Task Scheduler at logon, or install the runner as a Windows service via GitHub’sconfig.cmdoptions.
Run gh sr doctor to confirm SSH readiness (and native prerequisites on non-Linux hosts).