runpool README

The project's README from github.com/rhobuild/runpool. As one markdown file: README.md.

Runpool

CI CodeQL Go Docker Engine SQLite License Release

Runpool is a Docker-native control plane for autoscaling ephemeral CI runners on a single, capacity-bounded host. It translates provider demand into durable assignments, isolated per-job execution capsules, and optional repository-scoped cache lanes — without requiring Kubernetes.

Architecture

flowchart LR
    provider["GitHub Actions scale sets"] -->|"durable deliveries"| controller["Runpool controller"]
    controller -->|"Moby API"| host["Docker Engine 28+"]
    host --> capsule["Per-job capsule"]
    host --> gateway["Per-job egress gateway"]
    capsule -->|"DNS and HTTP proxy"| gateway
    capsule --> runtime["Runner + dockerd + job containers"]
    state[("SQLite state")] --- controller
    cache[("Repository cache lane")] -. "optional" .- capsule

The controller has no inbound service dependency. It maintains outbound provider sessions, reconciles durable state with Docker, and admits work only while scheduling and disk budgets are healthy. See the architecture guide for package boundaries, identity, recovery, and capacity semantics.

Why Runpool

Security boundary

Runpool is a resource-management, environment-hygiene, and network-policy boundary for CI workloads approved by the operator.

[!WARNING] Runpool is not a hostile code sandbox. It bounds and cleans up what an approved workload does; it does not contain code you would not have run.

Where that boundary ends:

Under public-internet-only, direct egress is denied. Proxy-aware HTTP clients can use absolute-form requests or CONNECT to allowed addresses on ports 80 and 443. CONNECT is an opaque tunnel; Runpool applies destination and port policy, not application-layer inspection. git+ssh, direct TCP, non-DNS UDP, IPv6, and other ports fail closed. Read the complete threat model before deployment.

Evaluate locally

Prerequisites are Go 1.26.0 or newer and a Linux host with rootful Docker Engine 28. The selected release-qualification target is documented in the support matrix.

git clone https://github.com/rhobuild/runpool.git
cd runpool
go build -trimpath -o runpool ./cmd/runpool
./runpool config validate --file internal/config/testdata/example.yaml
go test ./...

That builds the controller and checks a configuration. Reaching a job takes three more things: the two images, a target on GitHub, and a workflow that names the tier.

docker build -f build/capsule/Dockerfile -t runpool-capsule:dev .
docker build -f build/controller/Dockerfile -t runpool:dev .

[!NOTE] runpool-capsule:dev is the exact name a development build looks for. A release binary carries its capsule by digest and refuses to be pointed elsewhere; from source, this tag is the pairing.

Then a configuration, a credential, and the state directory:

RUNPOOL_GITHUB_URL=https://github.com/<owner>/<repo> \
RUNPOOL_GITHUB_TOKEN_FILE=/run/secrets/runpool/token \
RUNPOOL_HOST_TOPOLOGY=dedicated-daemon \
RUNPOOL_STATE_DIR="$PWD/state" \
./runpool doctor

RUNPOOL_STATE_DIR is $PWD/state here rather than the default /var/lib/runpool/state, which the deployed controller uses from inside its container and which a shell on the host cannot create without root.

doctor proves the host and the credential, and prints the runs-on label for every tier it would serve. Then ./runpool serve, and a workflow in that repository:

jobs:
  build:
    runs-on: runpool-standard

The complete example workflow shows what a job gets: its own Docker daemon, and egress through the relay. Deployment covers the GitHub side — which target shapes exist, what a runner group has to grant, and which credential to use — and the runbook covers operating it.

The reference Compose deployment asks for a released digest and refuses to start with no image at all; that it is a digest rather than a moving tag is yours to hold, and deployment covers verifying one. The images above are how a source checkout runs.

[!CAUTION] Do not use a production credential for local experimentation. A token that can reach real repositories reaches them from a host you are still testing.

Deployment

Runpool provides a canonical Docker Compose manifest for its headless controller: no domain, reverse-proxy route, or public port is required.

[!IMPORTANT] A version is release-qualified only after a no-skip qualification run on the exact platform frozen before its candidate in build/platform.lock.json. Each release carries the record that run produced, bound to its commit and to the image digests it publishes; release readiness lists the gates and the evidence each one leaves.

Platform Entry point
Docker Compose deploy/compose/compose.yaml
A Compose platform Deploy the canonical Compose file; a platform catalog entry, where the platform has one, derives its template from that same contract

Read the deployment guide before installing. The Compose contract reads an operator-managed configuration file; the example selects shared-daemon for a host shared with a platform, and illustrates an explicit reserve for colocated services. Measure that reserve before deployment. Choose dedicated-daemon only on an exclusive CI host.

Project documentation

Guide Purpose
Deployment Compose, platforms, credentials, and lifecycle
Product contract Guarantees, exclusions, and compatibility surfaces
Architecture Components, dependency direction, state, and recovery
Configuration Supported settings and validation rules
CLI reference Generated commands, flags, and exit-code behaviour
Support matrix Engine compatibility, release reference, and provider scope
Runbook Operations, recovery, backup, GC, and uninstall
Threat model Trust boundary, defences, one known weakness, and accepted exposure
ADRs Architectural decisions and measured constraints
Release readiness The objective gates a release must satisfy

Contributing and security

Read CONTRIBUTING.md before opening a pull request. Report security issues privately as described in SECURITY.md; never open a public issue for a suspected vulnerability.

Runpool is maintained by Rhobuild and licensed under the Apache License 2.0.

runpool (rhobuild/runpool)

Searching Database…

What is this?A Docker-native control plane that autoscales ephemeral CI runners on one capacity-bounded host, without Kubernetes: provider demand becomes durable job assignments and isolated per-job execution capsules.
How do I get it running?You need a Linux host with rootful Docker Engine 28 and Go 1.26.0+. Build the controller, run ./runpool doctor to check the host and credential, then ./runpool serve — or deploy via the provided Compose manifest.
What does the doctor command actually verify?It proves the host and the credential are good and prints the runs-on label for every tier it would serve, so you know what to name in your workflow before wiring up a job.
What happens if a job's outcome is ambiguous, say after a crash?Assignments are persisted before broker acknowledgement, so redelivery is idempotent, and ambiguous executions are held for operator review instead of being silently rerun.
Why run this on a single host instead of Kubernetes?That is the design point: autoscaling ephemeral runners on one capacity-bounded host. Runner, inner daemon, child containers and egress gateway all share an aggregate cgroup budget, with no Kubernetes cluster to operate.
Creator
rhobuild
Size
7.6 MB
Stars
376
Age
1mo
Commits · 90 Days