main 33a243c

CI runner for the ermite forge: GitHub Actions compatible jobs in Firecracker sandboxes

README.md

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.

Deployment options

Option Runs as Isolation Needs
Unprivileged container (docs/container.md) An unprivileged user (rootless Podman or Docker) vm, no jailer /dev/kvm, /dev/net/tun and CAP_NET_ADMIN in the container
Host service (below) root vm with the jailer, or container See the requirements below

The container image bundles the runner, Firecracker, the guest kernel and the base image. It is the simplest way to run vm mode without root on the host.

Requirements (host service)

  • 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 (host service)

In a container, the runner registers itself on its first start instead: see docs/container.md.

  1. On the ermite server, create a registration token:

    ermite token create my-runner --preset runner
    
  2. 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
    
  3. 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
    
  4. 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. https://, or http:// with ERMITE_RUNNER_INSECURE_HTTP=1
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.
  • As a host service, the state directory and all its parents must be owned by root and not writable by others.
  • In an unprivileged container, the jailer is off: the container and its user namespace confine Firecracker instead, and each job still runs in its own microVM.

Documentation

License

MIT. See LICENSE. Third-party attributions: THIRD_PARTY.md.

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 4

Tags 11

↑↓ moveenter openesc close