https://github.com/muhac/actions-runner-pool
gharp — Ephemeral, autoscaling self-hosted GitHub Actions runners for personal accounts. Multi-repo, Docker-only, no Kubernetes.
https://github.com/muhac/actions-runner-pool
ci-cd devops docker github-actions github-app homelab self-hosted self-hosted-runner
Last synced: 1 day ago
JSON representation
gharp — Ephemeral, autoscaling self-hosted GitHub Actions runners for personal accounts. Multi-repo, Docker-only, no Kubernetes.
- Host: GitHub
- URL: https://github.com/muhac/actions-runner-pool
- Owner: muhac
- License: apache-2.0
- Created: 2026-04-23T22:20:49.000Z (2 months ago)
- Default Branch: main
- Last Pushed: 2026-06-26T03:13:42.000Z (9 days ago)
- Last Synced: 2026-07-04T06:35:33.177Z (1 day ago)
- Topics: ci-cd, devops, docker, github-actions, github-app, homelab, self-hosted, self-hosted-runner
- Language: Go
- Homepage: https://muhac.github.io/actions-runner-pool/demo
- Size: 446 KB
- Stars: 2
- Watchers: 0
- Forks: 1
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE
- Agents: AGENTS.md
Awesome Lists containing this project
README
# gharp — GitHub Actions Runner Pool
A self-hosted, Docker-based pool of ephemeral GitHub Actions runners.
> 🍴 **Dogfooded** — this repo's own CI was running on a gharp pool during development!
## ✨ Features
* 🔐 **Self-hosted** — no external service dependency
* ♻️ **Ephemeral runners** — one job per runner, clean environment every time
* ⚡ **Autoscaling** — runners are created on-demand from webhook events
* 📊 **Built-in dashboard** — inspect jobs and runners (with prometheus metrics), retry/cancel controls, and in-place credential rotation (webhook secret / pem / client secret) gated behind a `ALLOW_ADMIN_EDIT` kill-switch
* 📦 **Multi-repository, personal-account support** — share compute across repos (not supported natively by GitHub)
> 👉 **[Try the dashboard live](https://muhac.github.io/actions-runner-pool/demo/)** — same UI, mock data, no backend. Click Cancel / Retry to see how the dashboard reacts.
## 🚀 Quick Start
### 1. Run gharp
Pre-built multi-arch image: [`muhac/gharp`](https://hub.docker.com/r/muhac/gharp).
**Minimal `docker run`**
```bash
docker run -d --name gharp \
-p 8080:8080 \
-e BASE_URL=https://gharp.example.com \
-v /var/run/docker.sock:/var/run/docker.sock \
-v gharp-data:/data \
muhac/gharp:1
```
`BASE_URL` must be a public HTTPS URL GitHub can reach, terminating at
the container's port 8080 (above mapped to the host's 8080). See
[`docs/configuration.md`](docs/configuration.md) for the full env-var reference.
**Recommended — Docker Compose**
```bash
# copy docker-compose.yml from this repo, then:
BASE_URL=https://gharp.example.com docker compose up -d
```
See [`docker-compose.yml`](docker-compose.yml) for the full reference configuration
(workdir cleanup, host Docker socket forwarding for ephemeral runners,
runner-container log caps, and `ADMIN_TOKEN` passthrough — set the
token in `.env`).
### 2. Create the GitHub App
Open `${BASE_URL}/setup` and click **Create GitHub App**. gharp drives
the GitHub App Manifest flow and persists the credentials locally.
> ⚠️ **Don't rename the auto-generated App name on GitHub.**
> gharp creates the App as `gharp-`; renaming it changes the slug,
> which breaks the install link gharp renders on `/setup`.
> The webhook keeps working — only the install link goes stale.
> To fix, delete the App on GitHub and re-run `/setup`.
> ⚠️ **`BASE_URL` is sticky.** It's baked into the GitHub App's webhook
> and OAuth-callback URLs at `/setup` time. Changing it later won't
> reconfigure the App — gharp will log a `BASE_URL drift` warning at
> startup. To migrate, re-run `/setup` (creating a fresh App) or revert
> `BASE_URL` to the original value.
### 3. Install the App
Pick the repos (or "All repositories") you want runners for and submit.
> ⚠️ **Self-hosted runners + public repos = remote code execution.**
> GitHub [explicitly recommends against](https://docs.github.com/en/actions/how-tos/manage-runners/self-hosted-runners/add-runners)
> using self-hosted runners with public repositories: any contributor
> who can open a PR can run arbitrary code on your machine.
> Only install the App on **private** repos you trust,
> and run gharp on a **dedicated VM / cloud instance / homelab node**.
> gharp drops public-repo `workflow_job` webhooks by default. To opt in,
> set `ALLOW_PUBLIC_REPOS=true` for all public repos, or use
> `REPO_ALLOWLIST=owner/repo` to bypass the guard for selected public repos.
### 4. Add a workflow
```yaml
jobs:
build:
runs-on:
- self-hosted
steps:
- uses: actions/checkout@v4
- run: echo "hello from $(hostname)"
test:
runs-on:
- self-hosted
- "gharp-test-${{ github.run_id }}-${{ github.run_attempt }}"
steps:
- run: echo "tests from $(hostname)"
```
Every `workflow_job` whose `runs-on` set is fully covered by
`RUNNER_LABELS` (default `self-hosted`; `self-hosted` is implicit on
every self-hosted runner so you don't need to list it) or a configured
dynamic label prefix (default `gharp-`) will get a fresh runner. Jobs
requiring a label this pool doesn't advertise are dropped — see
[`docs/configuration.md`](docs/configuration.md).
For the full deployment guide (from-source build, docker compose,
volumes, upgrades, ops APIs, troubleshooting), see [`docs/deploy.md`](docs/deploy.md).
## 🤔 Why?
GitHub does **not support "user-level" runners**.
* Runners are scoped to: repository, organization, or enterprise
This makes it hard to:
* share a runner across multiple repositories
* use self-hosted runners efficiently in personal accounts
* scale runners dynamically
💡 **This project solves that**
* Uses **GitHub App + webhook (`workflow_job`)**
* Dynamically creates **ephemeral runners per job**
* Automatically cleans up after execution
👉 You get **GitHub-hosted-like behavior on your own machine**
## 🏗️ Architecture
```mermaid
flowchart TB
subgraph setup["One-time setup"]
direction LR
U[User] -- POST manifest --> GH1[GitHub]
GH1 -- code + slug --> G1[gharp]
G1 -- App credentials --> DB[(sqlite)]
U -- install App --> GH1
end
subgraph runtime["Per-job runtime"]
direction LR
GH2[GitHub] -- workflow_job webhook --> G2[gharp]
G2 -- record job --> DB2[(sqlite)]
G2 -- registration token --> GH2
G2 -- docker run --> R[ephemeral runner]
R -- runs the job --> GH2
R -- one job, then exit --> X((removed))
end
setup --> runtime
```
See [`docs/architecture.md`](docs/architecture.md) for the full design,
including the GitHub App Manifest flow, sqlite job durability, and
permission scopes.
## 📦 Tech Stack
* Go (server)
* Docker (runner execution)
* GitHub App (auth + webhook)
## 📄 License
[Apache License 2.0](LICENSE) — Copyright 2026 Muhan Li.
## 🙌 Acknowledgements
* [GitHub Actions](https://docs.github.com/en/actions) / [self-hosted runners](https://docs.github.com/en/actions/concepts/runners/self-hosted-runners)
* [Docker Github Actions Runner](https://github.com/myoung34/docker-github-actions-runner)