main 33a243c

development.md RawHistoryFolder

Development

Build and check

CGO_ENABLED=0 go build -trimpath -o ermite-runner .
GOWORK=off go vet ./... && GOWORK=off go test -race ./...
  • The binary must be static: the sandbox has no libc of ours.
  • Unit tests need nothing special. The drivers are tested with a fake Firecracker and a fake Docker engine.
  • Protobuf: go generate ./internal/execstream. buf and protoc-gen-go are pinned as tool directives in go.mod. CI lints the proto and fails when the committed generated code differs.
  • ermite-runner sandbox-check [script] runs one stub job in the selected mode. It is the smoke test of an installation.

Integration tests

They are opt-in. They run the real stub executor. Output and exit codes come back, and nothing survives 10 jobs, including a killed one and a runner crash.

Variable Enables
ERMITE_RUNNER_IT_VM=1 VM tests. Need /dev/kvm, Firecracker, the kernel and the base image
ERMITE_RUNNER_IT_CONTAINER=1 Container tests. Need a rootful Docker daemon, root and the OCI image
ERMITE_RUNNER_IT_E2E=1 End-to-end job tests in internal/e2e
ERMITE_RUNNER_IT_NET=1 Packet-level firewall test
ERMITE_RUNNER_IT_DOCKER_API=1 Docker Engine calls against any daemon, including rootless
ERMITE_RUNNER_IT_REGISTRY=1 Live Docker Hub pulls through the registry proxy with curl (TestDockerHubCurl). Needs internet access
ERMITE_RUNNER_IT_NODE_DIR Directory with node-v24.21.0-linux-x64.tar.gz, for offline hosts
ERMITE_RUNNER_TEST_IMAGE Image used by the container and end-to-end tests

Build the kernel and images first: see images.

VM

# Unprivileged, in a user and network namespace. The jailer is off.
FIRECRACKER=/path/to/firecracker IMAGE_DIR=$PWD/tmp/image scripts/it-vm-userns.sh

# Privileged, with the jailer (the production configuration)
sudo env GOWORK=off ERMITE_RUNNER_IT_VM=1 ERMITE_RUNNER_ISOLATION=vm \
  ERMITE_RUNNER_IMAGE=$PWD/tmp/image ERMITE_RUNNER_STATE_DIR=/var/lib/ermite-runner-it \
  "$(command -v go)" test -count=1 -v ./internal/sandbox/integration -run VM

Container

sudo env GOWORK=off ERMITE_RUNNER_IT_CONTAINER=1 ERMITE_RUNNER_IMAGE=ermite-runner-ubuntu-26.04:latest \
  DOCKER_HOST=unix:///var/run/docker.sock "$(command -v go)" test -count=1 -v ./internal/sandbox/integration -run Container

End to end

The real executor runs in the real image, started by the container driver. It also works with a rootless Docker daemon, where mount and umount are replaced by stand-ins.

IMAGE_TAG=ermite-runner-ubuntu-26.04:dev scripts/build-image.sh tmp/image
ERMITE_RUNNER_IT_E2E=1 ERMITE_RUNNER_TEST_IMAGE=ermite-runner-ubuntu-26.04:dev GOWORK=off go test -race -count=1 ./internal/e2e
  • TestChainCorpus runs jobs of the ermite-conformance corpus through a fake server and compares the result with recordings made on github.com. It reads ERMITE_CONFORMANCE_DIR (default: a sibling checkout of ermite-conformance) and never writes to it.

Notes

  • Docker API. Production container mode supports Docker Engine and sysbox. Podman's compatibility API is not a drop-in: it cannot disable DNS on the network that the runner creates.
  • Jailer. The jailer needs --chroot-base-dir to exist. The driver creates <state dir>/jail. It leaves <state dir>/jail/<exec name> and /sys/fs/cgroup/firecracker behind.
  • Host firewalls. A firewall that rejects 53 on ertap* or erbr* breaks guest DNS. The symptom is dns silent in TestVMNetworkPolicy.

Measuring start-up on a small host

Build and register the runner container as usual, then constrain its CPU budget to reproduce a small host without changing the boot sequence:

docker run -d --name runner-timing --cpus=1 \
  --device /dev/kvm --device /dev/net/tun --cap-add NET_ADMIN \
  -v runner-timing-state:/var/lib/ermite-runner \
  --env-file runner.env -e ERMITE_RUNNER_LOG_FORMAT=json ermite-runner:<tag>

Use a registered state volume or supply the registration settings in runner.env; never put tokens in shared shell history. Podman accepts the same CPU limit (device access may need the CDI setup described in the container guide). Submit representative jobs, then extract completed-job measurements:

docker logs runner-timing 2>&1 | jq 'select(.message=="job finished")'

Compare sandbox_ms, the sb_* stages and guest_* boot stages across jobs. run_ms includes action downloads and step execution; archive misses have their own counters. See Job timing for the full field table.

source
1# Development
2
3## Build and check
4
5```sh
6CGO_ENABLED=0 go build -trimpath -o ermite-runner .
7GOWORK=off go vet ./... && GOWORK=off go test -race ./...
8```
9
10- The binary must be static: the sandbox has no libc of ours.
11- Unit tests need nothing special. The drivers are tested with a fake Firecracker and a fake Docker engine.
12- Protobuf: `go generate ./internal/execstream`. `buf` and `protoc-gen-go` are pinned as `tool` directives in `go.mod`. CI lints the proto and fails when the committed generated code differs.
13- `ermite-runner sandbox-check [script]` runs one stub job in the selected mode. It is the smoke test of an installation.
14
15## Integration tests
16
17They are opt-in. They run the real stub executor. Output and exit codes come back, and nothing survives 10 jobs, including a killed one and a runner crash.
18
19| Variable | Enables |
20| --- | --- |
21| `ERMITE_RUNNER_IT_VM=1` | VM tests. Need `/dev/kvm`, Firecracker, the kernel and the base image |
22| `ERMITE_RUNNER_IT_CONTAINER=1` | Container tests. Need a rootful Docker daemon, root and the OCI image |
23| `ERMITE_RUNNER_IT_E2E=1` | End-to-end job tests in `internal/e2e` |
24| `ERMITE_RUNNER_IT_NET=1` | Packet-level firewall test |
25| `ERMITE_RUNNER_IT_DOCKER_API=1` | Docker Engine calls against any daemon, including rootless |
26| `ERMITE_RUNNER_IT_REGISTRY=1` | Live Docker Hub pulls through the registry proxy with `curl` (`TestDockerHubCurl`). Needs internet access |
27| `ERMITE_RUNNER_IT_NODE_DIR` | Directory with `node-v24.21.0-linux-x64.tar.gz`, for offline hosts |
28| `ERMITE_RUNNER_TEST_IMAGE` | Image used by the container and end-to-end tests |
29
30Build the kernel and images first: see [images](images.md).
31
32### VM
33
34```sh
35# Unprivileged, in a user and network namespace. The jailer is off.
36FIRECRACKER=/path/to/firecracker IMAGE_DIR=$PWD/tmp/image scripts/it-vm-userns.sh
37
38# Privileged, with the jailer (the production configuration)
39sudo env GOWORK=off ERMITE_RUNNER_IT_VM=1 ERMITE_RUNNER_ISOLATION=vm \
40  ERMITE_RUNNER_IMAGE=$PWD/tmp/image ERMITE_RUNNER_STATE_DIR=/var/lib/ermite-runner-it \
41  "$(command -v go)" test -count=1 -v ./internal/sandbox/integration -run VM
42```
43
44### Container
45
46```sh
47sudo env GOWORK=off ERMITE_RUNNER_IT_CONTAINER=1 ERMITE_RUNNER_IMAGE=ermite-runner-ubuntu-26.04:latest \
48  DOCKER_HOST=unix:///var/run/docker.sock "$(command -v go)" test -count=1 -v ./internal/sandbox/integration -run Container
49```
50
51### End to end
52
53The real executor runs in the real image, started by the container driver. It also works with a rootless Docker daemon, where `mount` and `umount` are replaced by stand-ins.
54
55```sh
56IMAGE_TAG=ermite-runner-ubuntu-26.04:dev scripts/build-image.sh tmp/image
57ERMITE_RUNNER_IT_E2E=1 ERMITE_RUNNER_TEST_IMAGE=ermite-runner-ubuntu-26.04:dev GOWORK=off go test -race -count=1 ./internal/e2e
58```
59
60- `TestChainCorpus` runs jobs of the ermite-conformance corpus through a fake server and compares the result with recordings made on github.com. It reads `ERMITE_CONFORMANCE_DIR` (default: a sibling checkout of ermite-conformance) and never writes to it.
61
62## Notes
63
64- **Docker API.** Production `container` mode supports Docker Engine and sysbox. Podman's compatibility API is not a drop-in: it cannot disable DNS on the network that the runner creates.
65- **Jailer.** The jailer needs `--chroot-base-dir` to exist. The driver creates `<state dir>/jail`. It leaves `<state dir>/jail/<exec name>` and `/sys/fs/cgroup/firecracker` behind.
66- **Host firewalls.** A firewall that rejects `53` on `ertap*` or `erbr*` breaks guest DNS. The symptom is `dns silent` in `TestVMNetworkPolicy`.
67
68## Measuring start-up on a small host
69
70Build and register the [runner container](container.md) as usual, then constrain
71its CPU budget to reproduce a small host without changing the boot sequence:
72
73```sh
74docker run -d --name runner-timing --cpus=1 \
75  --device /dev/kvm --device /dev/net/tun --cap-add NET_ADMIN \
76  -v runner-timing-state:/var/lib/ermite-runner \
77  --env-file runner.env -e ERMITE_RUNNER_LOG_FORMAT=json ermite-runner:<tag>
78```
79
80Use a registered state volume or supply the registration settings in
81`runner.env`; never put tokens in shared shell history. Podman accepts the same
82CPU limit (device access may need the CDI setup described in the container
83guide). Submit representative jobs, then extract completed-job measurements:
84
85```sh
86docker logs runner-timing 2>&1 | jq 'select(.message=="job finished")'
87```
88
89Compare `sandbox_ms`, the `sb_*` stages and `guest_*` boot stages across jobs.
90`run_ms` includes action downloads and step execution; archive misses have their
91own counters. See [Job timing](service.md#job-timing) for the full field table.

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