Read-only mirror. Development happens at git.bnema.dev; issues and pull requests are closed here.
ermite-runner fetches jobs from an ermite server and runs workflows written for GitHub Actions (.github/workflows/*.yml). Each job runs in its own sandbox, destroyed when the job ends.
Isolation modes
| Mode |
Sandbox |
Needs |
vm |
A Firecracker microVM per job |
A usable /dev/kvm |
container |
A container per job with its own Docker daemon |
A rootful Docker Engine |
ERMITE_RUNNER_ISOLATION=auto is the default. It picks vm when /dev/kvm is usable and Firecracker, the guest kernel and the base image exist. Otherwise it picks container. The chosen mode and the reasons are logged at start.
Details: docs/sandboxes.md.
Requirements
- A Linux host. The runner runs as root.
ip, nft and mkfs.ext4 (e2fsprogs 1.43 or newer).
- Go, to build the runner.
- For
vm: /dev/kvm, firecracker and jailer in PATH, a guest kernel and a base image.
- For
container: a rootful Docker Engine and the job image. sysbox is optional: without it the runner uses a privileged container.
docs/images.md explains how to build the kernel and the images.
Build
The binary must be static:
CGO_ENABLED=0 go build -trimpath -o ermite-runner .
Register and run
-
On the ermite server, create a registration token:
ermite token create my-runner --preset runner
-
Install the binary, the unit and the settings:
sudo install -m 0755 ermite-runner /usr/local/bin/ermite-runner
sudo install -m 0644 contrib/ermite-runner.service /etc/systemd/system/
sudo install -d -m 0700 /etc/ermite-runner
sudo install -m 0600 /dev/stdin /etc/ermite-runner/env <<'ENV'
ERMITE_RUNNER_URL=https://ermite.example.com
ERMITE_RUNNER_NAME=runner-1
ERMITE_RUNNER_LABELS=ubuntu-latest,ubuntu-26.04
ERMITE_RUNNER_LOG_FORMAT=json
ENV
-
Register once. The token goes through standard input, so it is in no command line:
printf %s "$TOKEN" | sudo systemd-run --pipe --wait \
-p EnvironmentFile=/etc/ermite-runner/env \
-p StateDirectory=ermite-runner -p StateDirectoryMode=0700 \
/usr/local/bin/ermite-runner register --token-stdin
-
Start the service:
sudo systemctl enable --now ermite-runner
journalctl -u ermite-runner -f
ermite-runner run polls the server for jobs and runs each one in a sandbox, up to ERMITE_RUNNER_CAPACITY jobs at a time. On SIGTERM it cancels the running jobs, reports them and removes their sandboxes.
To check an installation without a server, run ermite-runner sandbox-check. It runs one stub script in the selected mode.
More on the service, logs and shutdown: docs/service.md.
Commands
| Command |
Purpose |
ermite-runner register [--token-stdin] [--token T] [--replace] |
Register with the server and store the runner token |
ermite-runner run |
Fetch and run jobs |
ermite-runner sandbox-check [script] |
Run one stub script in a sandbox |
ermite-runner version |
Print the version |
Key settings
| Variable |
Default |
Meaning |
ERMITE_RUNNER_URL |
|
Base URL of the ermite server. Must be https:// |
ERMITE_RUNNER_NAME |
host name |
Runner name |
ERMITE_RUNNER_LABELS |
|
Extra labels, comma separated |
ERMITE_RUNNER_ISOLATION |
auto |
auto, vm or container |
ERMITE_RUNNER_CAPACITY |
1 |
Jobs in parallel |
ERMITE_RUNNER_JOB_CPUS |
2 |
CPUs per job |
ERMITE_RUNNER_JOB_MEMORY |
4G |
Memory per job |
ERMITE_RUNNER_JOB_DISK |
64G |
Scratch disk per job |
ERMITE_RUNNER_STATE_DIR |
/var/lib/ermite-runner |
State directory |
All settings: docs/configuration.md.
Security notes
- Run only one runner per host.
- The registration token can read the code and secrets of every repository. Pass it through standard input.
- Without sysbox,
container mode uses a privileged container, which is equivalent to host root. Set ERMITE_RUNNER_DENY_PRIVILEGED=1 to refuse it.
- The state directory and all its parents must be owned by root and not writable by others.
Documentation
License
MIT. See LICENSE. Third-party attributions: THIRD_PARTY.md.
source
1<h1 align="center">ermite-runner</h1>
2
3<p align="center">
4 <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue?style=flat-square" alt="License: MIT"></a>
5 <a href="https://github.com/bnema/ermite-runner/commits/main"><img src="https://badgen.net/github/last-commit/bnema/ermite-runner/main?icon=github" alt="Last commit"></a>
6 <a href="https://github.com/bnema/ermite-runner/stargazers"><img src="https://badgen.net/github/stars/bnema/ermite-runner?icon=github" alt="GitHub stars"></a>
7</p>
8
9<p align="center">The CI runner of <a href="https://github.com/bnema/ermite">ermite</a>: runs GitHub Actions workflows, one microVM per job.</p>
10
11> Read-only mirror. Development happens at git.bnema.dev; issues and pull requests are closed here.
12
13---
14
15ermite-runner fetches jobs from an ermite server and runs workflows written for GitHub Actions (`.github/workflows/*.yml`). Each job runs in its own sandbox, destroyed when the job ends.
16
17## Isolation modes
18
19| Mode | Sandbox | Needs |
20| --- | --- | --- |
21| `vm` | A Firecracker microVM per job | A usable `/dev/kvm` |
22| `container` | A container per job with its own Docker daemon | A rootful Docker Engine |
23
24`ERMITE_RUNNER_ISOLATION=auto` is the default. It picks `vm` when `/dev/kvm` is usable and Firecracker, the guest kernel and the base image exist. Otherwise it picks `container`. The chosen mode and the reasons are logged at start.
25
26Details: [docs/sandboxes.md](docs/sandboxes.md).
27
28## Requirements
29
30- A Linux host. The runner runs as root.
31- `ip`, `nft` and `mkfs.ext4` (e2fsprogs 1.43 or newer).
32- Go, to build the runner.
33- For `vm`: `/dev/kvm`, `firecracker` and `jailer` in `PATH`, a guest kernel and a base image.
34- For `container`: a rootful Docker Engine and the job image. [sysbox](https://github.com/nestybox/sysbox) is optional: without it the runner uses a privileged container.
35
36[docs/images.md](docs/images.md) explains how to build the kernel and the images.
37
38## Build
39
40The binary must be static:
41
42```sh
43CGO_ENABLED=0 go build -trimpath -o ermite-runner .
44```
45
46## Register and run
47
481. On the ermite server, create a registration token:
49
50 ```sh
51 ermite token create my-runner --preset runner
52 ```
53
542. Install the binary, the unit and the settings:
55
56 ```sh
57 sudo install -m 0755 ermite-runner /usr/local/bin/ermite-runner
58 sudo install -m 0644 contrib/ermite-runner.service /etc/systemd/system/
59 sudo install -d -m 0700 /etc/ermite-runner
60 sudo install -m 0600 /dev/stdin /etc/ermite-runner/env <<'ENV'
61 ERMITE_RUNNER_URL=https://ermite.example.com
62 ERMITE_RUNNER_NAME=runner-1
63 ERMITE_RUNNER_LABELS=ubuntu-latest,ubuntu-26.04
64 ERMITE_RUNNER_LOG_FORMAT=json
65 ENV
66 ```
67
683. Register once. The token goes through standard input, so it is in no command line:
69
70 ```sh
71 printf %s "$TOKEN" | sudo systemd-run --pipe --wait \
72 -p EnvironmentFile=/etc/ermite-runner/env \
73 -p StateDirectory=ermite-runner -p StateDirectoryMode=0700 \
74 /usr/local/bin/ermite-runner register --token-stdin
75 ```
76
774. Start the service:
78
79 ```sh
80 sudo systemctl enable --now ermite-runner
81 journalctl -u ermite-runner -f
82 ```
83
84`ermite-runner run` polls the server for jobs and runs each one in a sandbox, up to `ERMITE_RUNNER_CAPACITY` jobs at a time. On SIGTERM it cancels the running jobs, reports them and removes their sandboxes.
85
86To check an installation without a server, run `ermite-runner sandbox-check`. It runs one stub script in the selected mode.
87
88More on the service, logs and shutdown: [docs/service.md](docs/service.md).
89
90## Commands
91
92| Command | Purpose |
93| --- | --- |
94| `ermite-runner register [--token-stdin] [--token T] [--replace]` | Register with the server and store the runner token |
95| `ermite-runner run` | Fetch and run jobs |
96| `ermite-runner sandbox-check [script]` | Run one stub script in a sandbox |
97| `ermite-runner version` | Print the version |
98
99## Key settings
100
101| Variable | Default | Meaning |
102| --- | --- | --- |
103| `ERMITE_RUNNER_URL` | | Base URL of the ermite server. Must be `https://` |
104| `ERMITE_RUNNER_NAME` | host name | Runner name |
105| `ERMITE_RUNNER_LABELS` | | Extra labels, comma separated |
106| `ERMITE_RUNNER_ISOLATION` | `auto` | `auto`, `vm` or `container` |
107| `ERMITE_RUNNER_CAPACITY` | `1` | Jobs in parallel |
108| `ERMITE_RUNNER_JOB_CPUS` | `2` | CPUs per job |
109| `ERMITE_RUNNER_JOB_MEMORY` | `4G` | Memory per job |
110| `ERMITE_RUNNER_JOB_DISK` | `64G` | Scratch disk per job |
111| `ERMITE_RUNNER_STATE_DIR` | `/var/lib/ermite-runner` | State directory |
112
113All settings: [docs/configuration.md](docs/configuration.md).
114
115## Security notes
116
117- Run only one runner per host.
118- The registration token can read the code and secrets of every repository. Pass it through standard input.
119- Without sysbox, `container` mode uses a privileged container, which is equivalent to host root. Set `ERMITE_RUNNER_DENY_PRIVILEGED=1` to refuse it.
120- The state directory and all its parents must be owned by root and not writable by others.
121
122## Documentation
123
124- [Configuration](docs/configuration.md): every environment variable
125- [Sandboxes](docs/sandboxes.md): `vm` and `container` modes, network policy, cleanup
126- [Service](docs/service.md): systemd unit, logs, shutdown
127- [Images](docs/images.md): job image, guest kernel
128- [Workflows](docs/workflows.md): job and service containers, cancellation, annotations
129- [Cache and artifacts](docs/cache-and-artifacts.md): Actions cache and artifacts v4
130- [Node.js externals](docs/externals.md): Node.js for JavaScript actions
131- [Development](docs/development.md): build, tests
132
133## License
134
135MIT. See [LICENSE](LICENSE). Third-party attributions: [THIRD_PARTY.md](THIRD_PARTY.md).