Ephemeral Action Runner (EPAR) keeps a warm pool of disposable GitHub Actions self-hosted runners on your own machines.
It is built for teams that want fast self-hosted Linux runners without keeping long-lived runner VMs around. EPAR creates an instance, registers it as an ephemeral GitHub runner, lets GitHub run one job on it, deletes the instance after that job, and creates a replacement.
flowchart LR
Workflow["GitHub Actions job"] --> Runner["Ephemeral runner"]
EPAR["EPAR supervisor"] --> Provider["Local provider<br/>Tart or WSL2"]
Provider --> Runner
Runner --> Job["One job runs"]
Job --> Delete["Delete instance"]
Delete --> Replace["Create replacement"]
Replace --> Runner
- Disposable runners: every runner is expected to handle one job and then disappear.
- Warm pool:
pool upkeeps ready runners online so jobs do not wait for a full image build. - Use spare hosts: turn a supported Mac or Windows machine into a pool of disposable Linux GitHub runners.
- Image control: the default image is runner-only, and extra tooling is added with explicit install scripts.
- GitHub App auth: the host uses a GitHub App to request short-lived runner registration tokens.
Use the provider that matches the machine you have available. EPAR is meant to make spare supported hosts useful as GitHub self-hosted runner capacity, not to make users compare providers in the abstract.
| Machine you have | EPAR provider | Runner architecture | Notes |
|---|---|---|---|
| Apple Silicon macOS | Tart | Ubuntu ARM64 | Good for Linux jobs that can run on ARM64. Workflows needing amd64-only Docker images should use a different label or handle cross-arch in the workflow. |
| Windows with WSL2 | WSL2 | Ubuntu x64 | Good for Linux jobs and Docker workflows that pull linux/amd64 images. Use for trusted internal jobs unless your environment has accepted the WSL isolation model. |
Future providers can fit the same model: if EPAR supports the machine, that machine can contribute disposable runner capacity with its own labels.
EPAR does not force Docker, browsers, Node, or project tools into the public default image.
| Image style | Config | Includes |
|---|---|---|
| Runner-only base | configs/tart.example.yml or configs/wsl.example.yml |
GitHub Actions runner and minimal runtime dependencies |
| Docker/browser | add scripts/guest/ubuntu/install-docker-browser.sh to image.customInstallScripts |
Docker Engine, Docker CLI, Compose v2, Buildx, and a Chromium-compatible browser |
| Web/E2E | configs/tart.web-e2e.example.yml or configs/wsl.web-e2e.example.yml |
Docker/browser plus Node.js/npm, zip, rsync, and mysql-client |
| Custom | add your own script path to image.customInstallScripts |
Whatever your script installs inside Ubuntu |
Example:
image:
customInstallScripts:
- scripts/guest/ubuntu/install-web-e2e.sh
- examples/custom-install/install-extra-apt-tools.sh-
Build the CLI:
go build -o ./bin/ephemeral-action-runner ./cmd/ephemeral-action-runner
-
Create a GitHub App that can manage organization self-hosted runners. See docs/github-app.md.
-
Copy one example config into
.local/config.ymland fill in the GitHub App values.macOS with Tart:
mkdir -p .local cp configs/tart.example.yml .local/config.yml
Windows with WSL2:
New-Item -ItemType Directory -Force .local Copy-Item configs/wsl.example.yml .local/config.yml
-
For WSL, export a clean Ubuntu 24.04 rootfs once:
New-Item -ItemType Directory -Force work/images wsl --install -d Ubuntu-24.04 --no-launch wsl --export Ubuntu-24.04 work/images/ubuntu-24.04-clean.rootfs.tar
-
Build the runner image:
./bin/ephemeral-action-runner image build --replace
If your selected install scripts use EPAR's Docker/browser or web/E2E scripts, first run:
./bin/ephemeral-action-runner image update-upstream
-
Verify two registered runners and clean them up:
./bin/ephemeral-action-runner pool verify --instances 2 --register-only --cleanup
-
Start a foreground pool:
./bin/ephemeral-action-runner pool up --instances 2
pool up is intentionally foreground. Keep it running while you want runners available. Stop it with Ctrl-C to clean up matching local instances and GitHub runner records.
sequenceDiagram
participant E as EPAR
participant P as Provider
participant R as Runner instance
participant G as GitHub
E->>P: clone and start instance
P-->>R: instance boots
E->>R: validate runtime
E->>G: request registration token
E->>R: configure runner as ephemeral
R-->>G: runner comes online
G->>R: assign one workflow job
R-->>G: runner exits after job
E->>P: delete instance
E->>G: remove stale runner record if needed
E->>P: create replacement
Cleanup is prefix-safe: EPAR only touches instances and GitHub runners whose names match pool.namePrefix.
Start with:
- Usage: commands for setup, image builds, verification, and pool operation.
- GitHub App Setup: minimum GitHub App permissions and config fields.
- Image Build: runner-only base images, install scripts, web/E2E images, and customization.
Provider details:
- Tart Provider: Apple Silicon macOS and Ubuntu ARM64.
- WSL Provider: Windows WSL2, rootfs tar images, and WSL caveats.
Operational context:
- Design: lifecycle and liveness model.
- Operations: logs, cleanup, and troubleshooting.
- Security: trust boundaries and secret handling.
- Background: why Linux guests are preferred for Docker and Compose-heavy jobs.
- Adding A Provider: provider interface expectations.
Tracked configs are examples only. Put real GitHub App IDs, private key paths, and local runner settings in .local/config.yml, configs/*.local.yml, or ~/.config/ephemeral-action-runner/config.yml; those paths are not intended for Git.