v0.5.1 25d29f5

cache-and-artifacts.md RawHistoryFolder

Cache and artifacts

The runner serves the GitHub Actions cache and artifacts v4 to jobs. Both use one channel per sandbox.

Channel

  • The services are reachable only from inside the sandbox.
  • They listen on the sandbox loopback (127.0.0.1:9089) and on the address of its own Docker bridge (docker0, typically 172.17.0.1:9089).
  • Nothing listens on a host network or port. The host firewall needs no rule.
  • A relay in the sandbox pipes each connection to the host over a channel that belongs to that sandbox:
    • vm: vsock port 9089.
    • container: cache.sock in the per-job directory.
  • A sandbox cannot reach another sandbox's channel.
  • Limits: 32 connections per sandbox, 128 in all. A transfer with no progress for 60 s is cut.

Variables

  • Node actions get ACTIONS_CACHE_URL, ACTIONS_RUNTIME_URL, ACTIONS_RUNTIME_TOKEN, ACTIONS_CACHE_MODE and ACTIONS_RESULTS_URL. run: steps get none, as on the official runner.
  • In job containers and docker actions the same variables point to the Docker bridge address.
  • The token is masked.
  • If the relay cannot start, the job runs without a cache.

Cache (API v1)

Works with actions/cache, setup-go and setup-node with cache:, and anything built on @actions/cache. The toolkit uses cache API v1 for any server that is not github.com, *.ghe.com or *.localhost. ACTIONS_CACHE_SERVICE_V2 is never set.

Setting Value
Storage <state dir>/cache, mode 0700/0600, written atomically
Size cap ERMITE_RUNNER_CACHE_MAX, default 10G. Least recently used entries are evicted
Off ERMITE_RUNNER_CACHE=0
Restart Entries are kept. Unfinished uploads are dropped
  • Scopes. Entries belong to (repository, ref, key, version). A job writes to its write ref and reads its read refs in priority order. Another repository's entries are never visible.
  • Server scope. The server sends the repository, write ref and read refs (feature ci.cache). The runner checks them: the repository must be the job's own, refs are full refs/... names, at most 8 read refs. An empty write ref makes the job read-only. A scope that fails the checks disables the cache for that job.
  • Old servers. Without a scope from the server, the scope is github.repository and the job ref, for reading and writing.
  • Duplicates. A second reservation of the same entry while one exists is a 409.
  • Reservations. Dropped after an hour without activity, or at the expiry of the token that made them.
  • Limits. Each entry is charged at least 4 KiB. At most 256 uploads in progress (32 per job). An entry being downloaded is never evicted. A reservation that cannot get room answers 503.
  • Bandwidth. A VM's cache traffic does not cross eth0. The runner limits it per sandbox at ERMITE_RUNNER_JOB_NET_BANDWIDTH. Containers have no limit.

Tokens

  • ACTIONS_RUNTIME_TOKEN is a JWT (HS256) bound to the job, repository, refs and an expiry (job timeout plus 15 min).
  • The key is random, made at start and kept in memory only.
  • Any invalid token gets a bare 403.
  • The token is not an ermite credential. The server refuses it.
  • Archive URLs carry their own HMAC and are served only to the job they were issued to.

Action archives

  • The host downloads an action archive once into <state dir>/actions-cache/, keyed by forge, repository and commit sha.
  • The sandbox gets a signed URL on the cache channel and extracts the archive.
  • The SHA-256 is checked on every read. A corrupt entry is dropped and fetched again.
  • Size cap: ERMITE_RUNNER_ACTIONS_CACHE_MAX, default 2G. 0 turns it off.
  • The sandbox never writes to this cache. Anything that cannot be cached is downloaded by the sandbox.

Artifacts (v4)

actions/upload-artifact@v4 and later, actions/download-artifact@v4 and later, and anything bundling @actions/artifact v2 work when the server has the feature ci.artifacts.

  • ermite stores the artifacts. The runner keeps nothing on disk.
  • The runner serves GitHub's results service (Twirp JSON plus Azure block blob requests on signed URLs) and translates it to the server with the job token.
  • The job token never enters the sandbox.
  • A job reaches only the artifacts of its own run.
  • Quota and size limits answer 412 (413 on a block) and are not retried. Server unavailable answers 503, which the toolkit retries.
  • Off: ERMITE_RUNNER_ARTIFACTS=0 or a server without ci.artifacts. Artifacts need the cache service, so ERMITE_RUNNER_CACHE=0 turns them off too.

GITHUB_SERVER_URL in artifact steps

  • @actions/artifact refuses to run on GitHub Enterprise Server, which it detects only from GITHUB_SERVER_URL.
  • In node steps of actions/upload-artifact and actions/download-artifact (any subpath), and of actions that contain the toolkit's GHES message, GITHUB_SERVER_URL is https://ermite.ghe.localhost.
  • Nothing resolves or connects to that host.
  • The github context, ${{ github.server_url }} and all other steps keep the real URL.

Not covered

  • upload-artifact below v4.3.1 and download-artifact below v4.1.2.
  • Artifacts v3.
  • The artifact-url output. It points at the fake host. The run page in ermite lists the artifacts.
  • Downloads from another run (run-id with github-token).
source
1# Cache and artifacts
2
3The runner serves the GitHub Actions cache and artifacts v4 to jobs. Both use one channel per sandbox.
4
5## Channel
6
7- The services are reachable only from inside the sandbox.
8- They listen on the sandbox loopback (`127.0.0.1:9089`) and on the address of its own Docker bridge (`docker0`, typically `172.17.0.1:9089`).
9- Nothing listens on a host network or port. The host firewall needs no rule.
10- A relay in the sandbox pipes each connection to the host over a channel that belongs to that sandbox:
11  - `vm`: vsock port 9089.
12  - `container`: `cache.sock` in the per-job directory.
13- A sandbox cannot reach another sandbox's channel.
14- Limits: 32 connections per sandbox, 128 in all. A transfer with no progress for 60 s is cut.
15
16### Variables
17
18- Node actions get `ACTIONS_CACHE_URL`, `ACTIONS_RUNTIME_URL`, `ACTIONS_RUNTIME_TOKEN`, `ACTIONS_CACHE_MODE` and `ACTIONS_RESULTS_URL`. `run:` steps get none, as on the official runner.
19- In job containers and docker actions the same variables point to the Docker bridge address.
20- The token is masked.
21- If the relay cannot start, the job runs without a cache.
22
23## Cache (API v1)
24
25Works with `actions/cache`, `setup-go` and `setup-node` with `cache:`, and anything built on `@actions/cache`. The toolkit uses cache API v1 for any server that is not `github.com`, `*.ghe.com` or `*.localhost`. `ACTIONS_CACHE_SERVICE_V2` is never set.
26
27| Setting | Value |
28| --- | --- |
29| Storage | `<state dir>/cache`, mode 0700/0600, written atomically |
30| Size cap | `ERMITE_RUNNER_CACHE_MAX`, default `10G`. Least recently used entries are evicted |
31| Off | `ERMITE_RUNNER_CACHE=0` |
32| Restart | Entries are kept. Unfinished uploads are dropped |
33
34- **Scopes.** Entries belong to (repository, ref, key, version). A job writes to its write ref and reads its read refs in priority order. Another repository's entries are never visible.
35- **Server scope.** The server sends the repository, write ref and read refs (feature `ci.cache`). The runner checks them: the repository must be the job's own, refs are full `refs/...` names, at most 8 read refs. An empty write ref makes the job read-only. A scope that fails the checks disables the cache for that job.
36- **Old servers.** Without a scope from the server, the scope is `github.repository` and the job ref, for reading and writing.
37- **Duplicates.** A second reservation of the same entry while one exists is a `409`.
38- **Reservations.** Dropped after an hour without activity, or at the expiry of the token that made them.
39- **Limits.** Each entry is charged at least 4 KiB. At most 256 uploads in progress (32 per job). An entry being downloaded is never evicted. A reservation that cannot get room answers `503`.
40- **Bandwidth.** A VM's cache traffic does not cross `eth0`. The runner limits it per sandbox at `ERMITE_RUNNER_JOB_NET_BANDWIDTH`. Containers have no limit.
41
42### Tokens
43
44- `ACTIONS_RUNTIME_TOKEN` is a JWT (HS256) bound to the job, repository, refs and an expiry (job timeout plus 15 min).
45- The key is random, made at start and kept in memory only.
46- Any invalid token gets a bare `403`.
47- The token is not an ermite credential. The server refuses it.
48- Archive URLs carry their own HMAC and are served only to the job they were issued to.
49
50### Action archives
51
52- The host downloads an action archive once into `<state dir>/actions-cache/`, keyed by forge, repository and commit sha.
53- The sandbox gets a signed URL on the cache channel and extracts the archive.
54- The SHA-256 is checked on every read. A corrupt entry is dropped and fetched again.
55- Size cap: `ERMITE_RUNNER_ACTIONS_CACHE_MAX`, default `2G`. `0` turns it off.
56- The sandbox never writes to this cache. Anything that cannot be cached is downloaded by the sandbox.
57
58## Artifacts (v4)
59
60`actions/upload-artifact@v4` and later, `actions/download-artifact@v4` and later, and anything bundling `@actions/artifact` v2 work when the server has the feature `ci.artifacts`.
61
62- **ermite stores the artifacts.** The runner keeps nothing on disk.
63- The runner serves GitHub's results service (Twirp JSON plus Azure block blob requests on signed URLs) and translates it to the server with the job token.
64- The job token never enters the sandbox.
65- A job reaches only the artifacts of its own run.
66- Quota and size limits answer `412` (`413` on a block) and are not retried. Server unavailable answers `503`, which the toolkit retries.
67- Off: `ERMITE_RUNNER_ARTIFACTS=0` or a server without `ci.artifacts`. Artifacts need the cache service, so `ERMITE_RUNNER_CACHE=0` turns them off too.
68
69### `GITHUB_SERVER_URL` in artifact steps
70
71- `@actions/artifact` refuses to run on GitHub Enterprise Server, which it detects only from `GITHUB_SERVER_URL`.
72- In node steps of `actions/upload-artifact` and `actions/download-artifact` (any subpath), and of actions that contain the toolkit's GHES message, `GITHUB_SERVER_URL` is `https://ermite.ghe.localhost`.
73- Nothing resolves or connects to that host.
74- The `github` context, `${{ github.server_url }}` and all other steps keep the real URL.
75
76### Not covered
77
78- `upload-artifact` below v4.3.1 and `download-artifact` below v4.1.2.
79- Artifacts v3.
80- The `artifact-url` output. It points at the fake host. The run page in ermite lists the artifacts.
81- Downloads from another run (`run-id` with `github-token`).

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