- Add deployment decision matrix with scenario-based recommendations - Add runner offline diagnostic procedure (logs→network→gh CLI→fixes) - Add runner lifecycle hooks (ACTIONS_RUNNER_HOOK_JOB_STARTED/COMPLETED) - Add Dependabot runner integration (allow toggle, runner group API flag) - Bump v1.0.1 → v1.0.2
7.6 KiB
Management & Operations
Runner Groups
Groups control which repos can access which runners at the org level.
Key operations:
- Create via UI: Settings → Actions → Runner groups → New runner group
- Create via gh CLI:
gh api --method POST orgs/<org>/actions/runner-groups \ -f name="<group-name>" -f visibility="selected" gh api --method POST orgs/<org>/actions/runner-groups \ -f name="<group-name>" -f visibility="all" - Register into group:
./config.sh --runnergroup <name> - Move runner between groups: GitHub UI → Settings → Actions → Runners → click runner → Runner group dropdown
- List groups:
gh api orgs/<org>/actions/runner-groups --jq '.runner_groups[].name' - Default group exists in every org; unnamed runners land there
- Restrict repo access: Selected repositories vs All repositories
- Delete group: all runners must be moved or removed first
Security: Groups prevent repos in group A from using runners in group B.
Labels
Labels control job routing at the runner level.
Default labels: self-hosted, OS (linux/windows/macOS), arch (x64/ARM/ARM32/ARM64)
Custom labels:
- Add at registration:
./config.sh --labels gpu,fast-ssd - Add/remove after registration: via GitHub UI
- Use in workflows:
runs-on: [self-hosted, linux, x64, gpu] - All labels must match (AND logic)
--no-default-labels: strip OS/arch auto-labels- Combine with groups:
runs-on: { group: ubuntu-runners, labels: ubuntu-24.04-16core }
Monitoring
Status in GitHub UI
- Idle: Connected, ready for jobs
- Active: Currently executing a job
- Offline: Not connected
Log Files
Located in _diag/ directory:
Runner_<timestamp>.log: App lifecycle, connection status, updatesWorker_<timestamp>.log: Per-job execution details
Journalctl (Linux systemd runners)
# Find service name
cat ~/actions-runner/.service
# Follow logs
sudo journalctl -u actions.runner.<scope>.<name>.service -f
Docker Runners
docker logs <container-name> --tail 20
gh CLI for Runner Status
# List runners for a repo
gh api repos/<owner>/<repo>/actions/runners --jq '.runners[] | "\(.name) (\(.status))"'
# List runners for an org
gh api orgs/<org>/actions/runners --jq '.runners[] | "\(.name) (\(.status))"'
# Check runner groups
gh api orgs/<org>/actions/runner-groups --jq '.runner_groups[].name'
Network Connectivity Check
./config.sh --check --url <url> --pat <pat_with_workflow_scope>
Tests each required endpoint (github.com, api.github.com, *.actions.githubusercontent.com, etc.) and outputs PASS/FAIL per endpoint. Logs in _diag/.
Runner Offline Diagnostic Procedure
When a runner shows "offline" in GitHub UI, follow these steps in order:
1. Check container/service logs
docker logs <container> --tail 50 # Docker runner
sudo journalctl -u actions.runner.* -f # systemd runner
Look for: connection failures, auth errors, or the runner starting then immediately losing connection.
2. Test network connectivity from the runner
./config.sh --check --url <url> --pat <pat>
This tests all required endpoints (github.com, api.github.com, *.actions.githubusercontent.com, etc.) and reports PASS/FAIL per endpoint. Logs in _diag/.
3. Verify runner registration with gh CLI
gh api repos/<owner>/<repo>/actions/runners --jq '.runners[] | "\(.name) (\(.status))"'
If the runner name doesn't appear, it was auto-removed (offline >14 days). With ACCESS_TOKEN set, the container should re-register on restart.
4. Check IP allow lists If your org uses IP allow lists, the runner's outbound IP must be added. Find it via:
curl -s ifconfig.me
Most likely cause for "worked yesterday, offline today": Network/firewall change or expired ACCESS_TOKEN.
Troubleshooting
| Issue | Likely Cause | Fix |
|---|---|---|
| 404 on registration | Using RUNNER_TOKEN instead of ACCESS_TOKEN |
Switch to ACCESS_TOKEN PAT |
| "Not configured" crash | No valid credentials; registration failed | Check logs; verify ACCESS_TOKEN or generate fresh token |
| "Could not find any self-hosted runner group named 'Default'" | Org uses differently-named group | Set RUNNER_GROUP to the actual group name |
| Ephemeral mode when not wanted | EPHEMERAL=0 (truthy in bash) |
Use EPHEMERAL=false |
docker: not found |
Docker not installed | Install Docker or the job doesn't need it |
Permission denied on Docker socket |
Runner user not in docker group | Add user to docker group or use root |
cd /home/user/path fails inside Docker container |
Runner can't see host paths | Write deploy configs inline; use Docker socket only |
| Runner offline >14 days | Auto-removed by GitHub | Register a new runner |
| GHCR pull "unauthorized" | No Docker registry auth in deploy job | Add docker/login-action@v4 with secrets.GITHUB_TOKEN |
Removing a Runner
If you have access to the runner machine:
# Run the removal command shown in GitHub UI
./config.sh remove --token <token>
If you don't have access: Use Force remove in the GitHub UI.
To re-register without re-downloading: Delete the .runner file in the runner directory. Runner can then be re-configured.
Runner Software Updates
- Default: Self-update enabled — runner auto-updates when a job is assigned or within 1 week
- Disabled:
--disableupdateflag — you manage updates via container image - 30-day window: If disabled, runner must be updated within 30 days or GitHub stops assigning jobs
- Critical security updates: Immediately block jobs until updated
- Recommendation for Docker runners: Set
DISABLE_AUTO_UPDATE=1and update the container image tag instead
Check latest release: https://github.com/actions/runner/releases
Runner Lifecycle Hooks
Runner lifecycle hooks let you run scripts before and after each job using environment variables.
| Hook | When it runs | Use cases |
|---|---|---|
ACTIONS_RUNNER_HOOK_JOB_STARTED |
After job assignment, before any workflow steps | Workspace cleanup, secret injection, telemetry start |
ACTIONS_RUNNER_HOOK_JOB_COMPLETED |
After all workflow steps, before job completion | Teardown, log shipping, metric collection |
Setup:
- Create a script (
.shor.ps1) anywhere on the runner machine - Make it executable:
chmod +x /path/to/script.sh - Set the env var in the runner's
.envfile, systemd unit, or Docker environment
Docker example:
environment:
- ACTIONS_RUNNER_HOOK_JOB_STARTED=/opt/runner/workspace-cleanup.sh
Cleanup script example:
#!/bin/bash
# Cleans workspace before each job
WORKSPACE="${RUNNER_WORKSPACE:-${GITHUB_WORKSPACE:-/home/runner/_work}}"
rm -rf "${WORKSPACE:?}"/* || true
Testing: Run a job and check workflow logs for a "Set up runner" section containing "Hook output:". Non-zero exit from JOB_STARTED prevents the job from running.
Note: JOB_COMPLETED runs synchronously during job cleanup — it cannot be used to destroy the runner itself. Use --ephemeral for that.
Common Pitfalls
- ACCESS_TOKEN vs RUNNER_TOKEN — most common failure mode
- EPHEMERAL=false as string, not
0— bash truthiness trap - Missing RUNNER_GROUP — fails to register if group doesn't exist
- Volume cleanup wipes credentials —
docker compose down -vremoves named volumes; ACCESS_TOKEN auto-recovers - Docker container filesystem — runner cannot see host paths
- Ubuntu 20.04 Python 3.8 —
dict | Nonesyntax fails; usefrom __future__ import annotations - Hugo modules need Go — vendor modules or add
actions/setup-go@v5