{"id":51526851,"url":"https://github.com/ivuorinen/github-actions-runner-setup","last_synced_at":"2026-07-08T22:30:30.525Z","repository":{"id":354743842,"uuid":"1208962628","full_name":"ivuorinen/github-actions-runner-setup","owner":"ivuorinen","description":"This repository provides ephemeral self-hosted GitHub Actions runners using a GitHub App for registration.","archived":false,"fork":false,"pushed_at":"2026-07-02T23:45:34.000Z","size":216,"stargazers_count":0,"open_issues_count":3,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-03T01:25:04.978Z","etag":null,"topics":["github-actions","github-actions-runner","github-actions-self-hosted-runner","self-hosted-runner"],"latest_commit_sha":null,"homepage":"","language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ivuorinen.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.md","code_of_conduct":".github/CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-13T00:47:55.000Z","updated_at":"2026-07-02T23:45:37.000Z","dependencies_parsed_at":null,"dependency_job_id":"2406a2af-9aba-45bb-881d-0ac4f403e5a8","html_url":"https://github.com/ivuorinen/github-actions-runner-setup","commit_stats":null,"previous_names":["ivuorinen/github-actions-runner-setup"],"tags_count":0,"template":false,"template_full_name":"ivuorinen/github-base-template","purl":"pkg:github/ivuorinen/github-actions-runner-setup","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ivuorinen%2Fgithub-actions-runner-setup","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ivuorinen%2Fgithub-actions-runner-setup/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ivuorinen%2Fgithub-actions-runner-setup/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ivuorinen%2Fgithub-actions-runner-setup/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ivuorinen","download_url":"https://codeload.github.com/ivuorinen/github-actions-runner-setup/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ivuorinen%2Fgithub-actions-runner-setup/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35280681,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-08T02:00:06.796Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["github-actions","github-actions-runner","github-actions-self-hosted-runner","self-hosted-runner"],"created_at":"2026-07-08T22:30:29.860Z","updated_at":"2026-07-08T22:30:30.520Z","avatar_url":"https://github.com/ivuorinen.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Self-hosted GitHub Actions runners\n\nEphemeral self-hosted GitHub Actions runners. Each runner registers with\nGitHub via a **GitHub App**, executes exactly one job, deregisters, and\nDocker restarts a fresh container. The system is designed for Coolify\nbut works on any Docker Engine 20.10+ host.\n\n## Why this exists\n\n- GitHub Personal Access Tokens for runner registration are clunky, long-lived,\n  and rotate poorly. GitHub App authentication mints short-lived\n  installation tokens with narrowly-scoped permissions, refreshed on every\n  container start.\n- Ephemeral runners give you fresh state per job, which means workflows\n  cannot poison each other through `_work` cache, environment leftovers,\n  or leftover docker images.\n- Docker pull caching is preserved across job boundaries via a shared\n  `socket-proxy` sidecar — runs that depend on the same base images are\n  fast.\n\n## Documentation map\n\n- **[`SETUP.md`](SETUP.md)** — step-by-step deployment from scratch to a\n  running fleet.\n- **[`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md)** — how the system\n  fits together, container lifecycle, token chain.\n- **[`docs/OPERATIONS.md`](docs/OPERATIONS.md)** — day-2 ops, scaling,\n  credential rotation, decommissioning.\n- **[`docs/TROUBLESHOOTING.md`](docs/TROUBLESHOOTING.md)** — common\n  failures and fixes.\n- **[`docs/ENVIRONMENT-VARIABLES.md`](docs/ENVIRONMENT-VARIABLES.md)** —\n  every env var the system reads, with defaults and sensitivity.\n- **[`docs/SECURITY.md`](docs/SECURITY.md)** — threat model and\n  defensive choices.\n- **[`docs/SECURITY-REVIEW-2026-04-20.md`](docs/SECURITY-REVIEW-2026-04-20.md)** —\n  formal security review and remediation status.\n- **[`CONTRIBUTING.md`](CONTRIBUTING.md)** — how to make changes safely.\n- **[`SECURITY.md`](SECURITY.md)** (root) — how to report a vulnerability.\n- **[`CHANGELOG.md`](CHANGELOG.md)** — release notes and notable changes.\n\n## Features\n\n- **GitHub App authentication.** No long-lived PATs. JWT → installation\n  token → registration token, all short-lived.\n- **Ephemeral runners.** One job per container. Auto-deregister on\n  exit.\n- **Shared Docker image cache.** All runners reach the host Docker\n  daemon through a single `socket-proxy` sidecar, so layer caches are\n  shared. Cross-runner inspection is blocked.\n- **Hardened by default.** `cap_drop: ALL` + minimal `cap_add`,\n  `no-new-privileges`, ulimits, `pids_limit`, `mem_limit`, tmpfs for\n  scratch space. The runner user (UID 1001) cannot read the GitHub App\n  private key (root-owned, mode 600).\n- **Coolify-compatible.** `env_file: required: false` so Coolify can\n  inject env vars directly. No hard dependency on Coolify.\n- **Renovate keeps base images and pre-commit pins current.**\n\n## Quick start\n\n1. Create a GitHub App with:\n   - **Org scope**: *Self-hosted runners: Read and write* + *Metadata: Read*\n   - **Repo scope**: *Administration: Read and write* + *Metadata: Read*\n2. Install the App in the target org or repo. Note the App ID and\n   Installation ID.\n3. Copy the PEM to the Docker host, owned by root, mode 600:\n\n   ```bash\n   cp my-app.pem /etc/github-app/private-key.pem\n   chown 0:0 /etc/github-app/private-key.pem\n   chmod 600 /etc/github-app/private-key.pem\n   ```\n\n4. Copy `.env.example` to `.env` and fill in `GITHUB_APP_ID`,\n   `GITHUB_APP_INSTALLATION_ID`, `GITHUB_APP_PRIVATE_KEY_HOST_PATH`,\n   `RUNNER_SCOPE`, and either `GITHUB_ORG` or\n   `GITHUB_REPO_OWNER`/`GITHUB_REPO_NAME`.\n5. `make up` (or `docker compose up -d`).\n6. Verify the runners appear in your org or repo's Actions settings.\n7. Target the runners in workflows with `runs-on:` labels that match\n   `RUNNER_DEFAULT_LABELS` + the runner's `RUNNER_\u003cN\u003e_LABELS`.\n\nFull step-by-step guide in [`SETUP.md`](SETUP.md).\n\n## Requirements\n\n- Docker Engine 20.10+\n- Docker Compose v2.24+ (required for `env_file: required: false` syntax)\n- A GitHub App with the permissions listed above\n- A Docker host where `/var/run/docker.sock` is mountable (the\n  socket-proxy needs this)\n\n## Example workflow\n\n```yaml\nname: lint\non:\n  push:\n  pull_request:\n\njobs:\n  lint:\n    runs-on: [self-hosted, linux, x64, docker, ephemeral, lint, small]\n    steps:\n      - uses: actions/checkout@v4\n      - run: docker version\n      - run: echo \"Runner is working\"\n```\n\nThe `runs-on:` label set must exactly match (case-sensitive)\n`RUNNER_DEFAULT_LABELS` + at least one runner's `RUNNER_\u003cN\u003e_LABELS`.\n\n## Adding more runners\n\nCopy a `runner-*` service block in `docker-compose.yml` and change:\n\n- service name (`runner-4`)\n- hostname (uses `RUNNER_CONTAINER_PREFIX`)\n- `RUNNER_INSTANCE_NAME` → `RUNNER_4_NAME`\n- `RUNNER_EXTRA_LABELS` → `RUNNER_4_LABELS`\n\nFull template in [`SETUP.md`](SETUP.md#9-add-more-runners).\n\n`RUNNER_DEFAULT_LABELS` provides the shared base labels; each runner's\n`RUNNER_\u003cN\u003e_LABELS` adds runner-specific labels combined inside the\ncontainer by `entrypoint.sh` (mapped to `RUNNER_EXTRA_LABELS`). If you\nprefer to bypass that pattern you can set `RUNNER_LABELS` directly in\nthe service environment instead.\n\n## Security at a glance\n\n- The GitHub App PEM is owned by **root** on the host (mode 600). Workflow\n  jobs (UID 1001) cannot read it. See `docs/SECURITY-REVIEW-2026-04-20.md`\n  finding **H-1** for the full analysis.\n- Workflow jobs reach the Docker daemon through a `socket-proxy`\n  sidecar that allows image cache operations only. `CONTAINERS` is\n  intentionally disabled so jobs cannot inspect or exec into sibling\n  runners. See `docs/SECURITY.md` and the socket-proxy block in\n  `docker-compose.yml`.\n- All Linux capabilities are dropped by default. Re-added: `CHOWN`,\n  `DAC_OVERRIDE`, `FOWNER`, `SETGID`, `SETUID`, `KILL`. See the\n  capability block in `docker-compose.yml` for the rationale.\n- `no-new-privileges: true` prevents setuid-based privilege escalation\n  inside the container.\n- The runner is **ephemeral** — accepts exactly one job and exits. State\n  is wiped between runs.\n\nReport vulnerabilities privately — see [`SECURITY.md`](SECURITY.md).\n\n## Operations\n\n- `make build` — build the image (uses the digest-pinned base).\n- `make up` / `make down` — lifecycle.\n- `make logs` — tail every service.\n- `make lint` — run the full pre-commit suite locally.\n- `make audit` / `make audit-image` — checkov + trivy scans.\n\nDay-2 details, scaling, and credential rotation: [`docs/OPERATIONS.md`](docs/OPERATIONS.md).\n\n## License\n\nMIT — see [`LICENSE.md`](LICENSE.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fivuorinen%2Fgithub-actions-runner-setup","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fivuorinen%2Fgithub-actions-runner-setup","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fivuorinen%2Fgithub-actions-runner-setup/lists"}