Install Docker Engine and the Docker Compose v2 plugin on each Linux Docker runner host.
On Linux production hosts, create the runner workspace directory before starting pools:
make init-workdirThis creates /home/runner/actions-runner/_work with ownership for the runner user inside containers. See Production Setup Guide for the full Linux VM layout.
For a full list of Mac Docker Desktop issues and fixes, see Self-Hosted Runner Issues and Solutions.
Recommended baseline:
- 4 CPU and 8 GB RAM per heavy runner.
- 40 GB or more free disk for Docker images, caches, and workspaces.
- Outbound HTTPS to
github.comandapi.github.com.
Check Docker socket permissions:
stat -c '%g %n' /var/run/docker.sockIf needed, put that group ID in .env as DOCKER_GID.
Add repositories and pools in runners.config.json.
Create it from the public example if needed:
make config-initUse at least three replicas for heavy monorepos:
{
"id": "linux-x64-docker",
"enabled": true,
"runtime": "docker",
"replicas": 3
}Create a token owned by a GitHub account with admin access to each configured repository.
Use one of:
- Classic PAT with
reposcope. - Fine-grained PAT with repository access and
Administrationwrite permission.
Set it only in .env as GITHUB_TOKEN.
Never commit .env.
One-hour tokens from GitHub's "Add new self-hosted runner" page are only for short manual tests:
Set short manual test tokens only in GITHUB_RUNNER_REGISTRATION_TOKEN.
For production, leave GITHUB_RUNNER_REGISTRATION_TOKEN empty and use GITHUB_TOKEN so containers can fetch fresh registration and removal tokens.
make env
make config-init
make list-pools
make validateIf .env has a real GITHUB_TOKEN, also run:
make doctorRecommended config-driven mode:
make apply
make ps-generated
make logs-generatedSingle-pool compatibility mode:
make build
make up SINGLE_POOL_REPLICAS=1
make logsConfirm runners appear in the GitHub repository runner settings page for each configured repository.
Runner counts live in runners.config.json. Do not scale production pools from .env.
For example, to increase an already-running pool from 3 to 5 runners, edit the target pool:
"replicas": 5Apply the config again:
make applyCheck containers:
make ps-generatedCheck GitHub:
gh api repos/<owner>/<repo>/actions/runners \
--jq '.runners[] | [.name, .status, .busy] | @tsv'One runner container equals one concurrent GitHub Actions job.
Generated config mode:
make stopSingle-pool mode:
make downThe entrypoint requests a remove token and deregisters the runner during shutdown when GITHUB_TOKEN is available. If a server is killed abruptly, remove stale offline runners from GitHub settings.
Remove all runner containers, Compose volumes, built images, and the generated compose file:
make destroyAlso remove the host workspace (runnerWorkHostPath / actions-runner tree):
make destroy-alldestroy preserves .env and runners.config.json. After destroy, start fresh with:
make init-workdir # if using host _work bind mounts
make applyOptional flags for scripts/destroy.sh:
| Variable | Default | Effect |
|---|---|---|
DESTROY_WORKDIR |
0 |
Remove host actions-runner workspace (destroy-all sets 1) |
DESTROY_GENERATED |
1 |
Delete compose.generated.yaml |
DESTROY_IMAGES |
1 |
Remove runner images from config |
DESTROY_GITHUB_OFFLINE |
1 |
Delete offline runners from GitHub for configured repos |
- Create a new GitHub token.
- Update
.env. - Restart the runner pools:
make restart-generatedUpdate the matching entry in runnerPackages:
versionarchiveurlsha256
Then rebuild and apply:
make applyOn Linux Docker hosts, install and manage the service through Make:
make systemd-install
make systemd-enable
make systemd-startInspect the service:
make systemd-status
make systemd-logsThe unit runs make apply on start and make down-generated on stop from /opt/github-runner-manager.
Runner does not appear in GitHub:
- Run
make list-pools. - Confirm the pool is
enabled: true. - Confirm the token has repository admin access.
- Check logs with
make logs-generated.
Docker commands fail inside jobs:
- Confirm
/var/run/docker.sockis mounted. - Confirm the host Docker daemon is running.
- Set
DOCKER_GIDtostat -c '%g' /var/run/docker.sockand restart.
Jobs do not pick this runner:
- Ensure workflow
runs-onincludes labels from the selected pool:
runs-on: [self-hosted, linux, x64, docker, project-id]Service container or job container mount errors (_work is not shared from the host):
- See Production Setup Guide for the Linux VM layout and
_workhost bind mount.
pnpm install is slow with reused 0 but step succeeds:
- See Issue 12. Monorepo filtered installs are an app-side fix; run
make applyfor optional hostpnpm-storemount.
checkout fails with could not read Username for 'https://github.com':
- On macOS path-rewrite pools, run
make applyso runners useworkspaces/<id>/_work(no_worksymlink). See Issue 10. - If it persists (fork PRs, restricted token defaults), add
permissions: contents: readand/ortoken: ${{ github.token }}on the checkout step.
Runner exits after a job:
- This is expected when
RUNNER_EPHEMERAL=true. - Compose restarts the container and it registers a fresh runner.