A self-hosted runner executes CI jobs on your own hardware. It polls Anvil for queued work, runs each job in an isolated workspace, and streams logs back in real time. This page covers installing, configuring, operating, and removing a runner.
The runner ships in the anvil binary, which is also the CLI. The same binary serves as the runner daemon and the administrative client.
Install
The install script downloads the binary for your platform, verifies its SHA256 checksum, and installs it as anvil in /usr/local/bin (when that directory is writable or you run as root) or ~/.local/bin:
curl -fsSL https://anvil.fangorn.io/runner/install.sh | sh
The script requires curl and git to be present. It writes the binary to a temporary file, verifies it, then moves it into place, so a failed download never clobbers an existing install. If the install directory is not on your PATH, the script prints a warning telling you to add it.
After installing, register the runner and start it as a service:
anvil runner configure --url https://anvil.fangorn.io --token <TOKEN>
anvil runner service install
anvil runner service svc-start
Generate <TOKEN> in your repo settings under Settings → CI → Runners, or with the CLI (see Registration tokens).
Preview without making changes
Set ANVIL_INSTALL_DRY_RUN=1 to print every planned action and exit 0 without touching anything:
curl -fsSL https://anvil.fangorn.io/runner/install.sh \
| ANVIL_INSTALL_DRY_RUN=1 sh
Upgrade
The install script is idempotent. Re-running it downloads and swaps in the latest binary, regenerates the service file, and preserves the existing config:
curl -fsSL https://anvil.fangorn.io/runner/install.sh | sh
After upgrading a binary that runs as a service, restart the service so it picks up the new executable:
anvil runner service svc-restart
Install environment variables
These variables modify the install script’s behavior. Set them on the sh invocation, as shown in the dry-run example above.
| Variable | Default | Effect |
|---|---|---|
ANVIL_INSTALL_DIR |
/usr/local/bin or ~/.local/bin |
Force the install path. The directory is created if missing. |
ANVIL_INSTALL_DRY_RUN |
0 |
Print planned actions, modify nothing, exit 0. |
ANVIL_INSTALL_NO_MIGRATE |
0 |
Skip migration from the legacy anvil-runner binary. |
ANVIL_INSTALL_REQUIRE_CHECKSUM |
0 |
Hard-fail if the latest release publishes no SHA256SUMS file. By default a missing checksum prints a warning and continues. |
ANVIL_INSTALL_TOLERATE_SERVICE_FAILURE |
0 |
Downgrade service reinstall, start, and post-start verification failures from errors to warnings. Set to 1 for CI or environments where the host cannot run a service. |
ANVIL_SERVER_URL |
https://anvil.fangorn.io |
Override the URL the script fetches the binary and checksums from. |
Configure the runner
anvil runner configure registers the machine with Anvil and writes a config file to ~/.anvil-runner/config.json. Pass --config <path> to use a different location.
anvil runner configure \
--url https://anvil.fangorn.io \
--token <TOKEN> \
--name builder-01 \
--labels self-hosted,linux,arm64 \
--parallel 2 \
--work-dir /srv/anvil-runner/work
| Flag | Default | Description |
|---|---|---|
--url |
(required) | Anvil server URL to register against. |
--token |
from ANVIL_RUNNER_TOKEN |
Registration token. If omitted, the runner reads ANVIL_RUNNER_TOKEN from the environment. |
--name |
machine hostname | Display name for the runner. |
--labels |
self-hosted |
Comma-separated labels. A job’s runs_on in .anvil.yml matches against these. See Labels and job matching. |
--parallel |
1 |
Maximum number of jobs the runner executes concurrently. |
--work-dir |
~/.anvil-runner/_work |
Directory where job workspaces are checked out. |
--config |
~/.anvil-runner/config.json |
Config file path. |
The runner registers by POSTing to /api/v1/runners/register with its name, labels, OS, and architecture, then stores the returned runner_id and runner_token in the config file. The poll interval is 5000 ms and the heartbeat interval is 30000 ms.
Labels and job matching
Labels connect jobs to runners. A job declares the labels it needs with runs_on in .anvil.yml; a runner advertises the labels it has via --labels.
A runner can claim a job when one of these holds:
- The job declares no
runs_onlabels. - Every label in the job’s
runs_onis present in the runner’s label set. The job’s required labels must be a subset of the runner’s labels.
A runner with extra labels the job does not require can still claim the job. A runner missing any label the job requires cannot. The default label self-hosted applies when you pass no --labels.
steps:
- name: build-arm
run: make build
runs_on: [self-hosted, arm64]
That step is claimable by the runner configured with --labels self-hosted,linux,arm64 above, because arm64 and self-hosted are both in the runner’s set.
Run the runner
To run in the foreground without a service, use anvil runner start:
anvil runner start
| Flag | Default | Description |
|---|---|---|
--once |
off | Run one job, then exit. |
--ephemeral |
off | Deregister from the server after one job. |
--cleanup |
never |
Workspace cleanup strategy: always, never, or on-success. |
--config |
~/.anvil-runner/config.json |
Config file path. |
To run the runner unattended, install it as a service (see below) rather than relying on start in a terminal.
Operate as a service
anvil runner service install generates an OS service unit that runs anvil runner start. On macOS it writes a launchd plist at ~/Library/LaunchAgents/com.anvil.runner.plist. On Linux it writes a systemd unit — /etc/systemd/system/anvil-runner.service when run as root, or ~/.config/systemd/user/anvil-runner.service otherwise — and enables it. The systemd unit restarts the runner automatically (Restart=always).
The service subcommands use svc- prefixes so they do not collide with the runner-execution verbs:
| Command | Description |
|---|---|
anvil runner service install |
Install the launchd plist or systemd unit. |
anvil runner service uninstall |
Disable and remove the service unit. |
anvil runner service svc-start |
Start the service. |
anvil runner service svc-stop |
Stop the service. |
anvil runner service svc-restart |
Restart the service. Use this after upgrading the binary. |
anvil runner service svc-status |
Print service status from systemctl or launchctl. |
On macOS, svc-restart is emulated as an unload followed by a load, since launchctl has no restart verb.
Service logs are written under ~/.anvil-runner/:
| File | Contents |
|---|---|
~/.anvil-runner/runner.log |
Standard output. |
~/.anvil-runner/runner.err.log |
Standard error. Check this first when a service starts but the daemon is not alive. |
Inspect the local runner
anvil runner status prints the configuration this machine is running with, read from the config file:
anvil runner status
It reports the server URL, runner ID, name, labels, work directory, parallelism, poll and heartbeat intervals, and the config file path. Pass --config <path> to read a config file in a non-default location.
Manage runners on the server
These commands act on registered runners over the API and authenticate with your personal access token (see the CLI reference for token setup). They are distinct from the runner-execution commands above. The same actions are available in the web UI at Settings → CI → Runners for a repo.
# List runners for an org or a repo
anvil runner list --org fangorn
anvil runner list --repo fangorn/anvil
# View one runner
anvil runner view <RUNNER_ID>
# Update labels or name
anvil runner update <RUNNER_ID> --labels self-hosted,linux,gpu
anvil runner update <RUNNER_ID> --name builder-02
# Remove a runner
anvil runner remove <RUNNER_ID>
| Command | Description |
|---|---|
anvil runner list --org <slug> |
List runners registered to an org. |
anvil runner list --repo <org>/<repo> |
List runners registered to a repo. |
anvil runner view <id> |
Show one runner’s status, labels, OS, arch, and last heartbeat. |
anvil runner update <id> [--labels ...] [--name ...] |
Update a runner’s labels or name. |
anvil runner remove <id> |
Deregister a runner from the server. |
The --org and --repo flags on list are alternatives; specify one. A <RUNNER_ID> is the full ID shown in anvil runner list or in the web UI.
Registration tokens
A registration token authorizes one runner to register against an org or a repo. Generate one in the web UI or with the CLI.
In the web UI, open the repo’s Settings → CI → Runners, click New Runner, choose whether the token is scoped to the repository or the organization, and click Generate Token. The token is shown once, expires in one hour, and can be used a single time. Copy it before leaving the page.
From the CLI, use anvil runner token. The --org and --repo flags are mutually exclusive; specify exactly one.
anvil runner token --repo fangorn/anvil
anvil runner token --org fangorn
Pass the printed token to anvil runner configure --token, or export it as ANVIL_RUNNER_TOKEN and omit the flag.
Uninstall
Stop the service, remove the service unit, deregister from the server, and delete the binary and config:
anvil runner service svc-stop
anvil runner service uninstall
anvil runner unconfigure
rm "$(command -v anvil)"
rm -rf ~/.anvil-runner
anvil runner unconfigure sends a deregistration request to the server and removes the local config file. If the server is unreachable it warns and still removes the local config.
Command reference
Runner-execution commands
These run on the runner host and use the runner’s own credentials from the config file.
| Command | Description |
|---|---|
anvil runner configure |
Register this machine as a CI runner and write the config file. |
anvil runner start |
Poll for and execute CI jobs. |
anvil runner status |
Print the runner’s configuration. |
anvil runner unconfigure |
Deregister from the server and remove the config file. |
anvil runner service ... |
Manage the OS service (see Operate as a service). |
Install endpoints
The server exposes unauthenticated endpoints so runners can self-install. The install script uses them; you do not normally call them directly.
| Endpoint | Returns |
|---|---|
GET /runner/install.sh |
The shell install script, as text/plain. |
GET /runner/download?os=<os>&arch=<arch> |
The platform binary for the latest release, streamed as application/octet-stream. |
GET /runner/version |
JSON {"version": ..., "platforms": [...]} for the latest release. |
GET /runner/checksums |
The SHA256SUMS file for the latest release, as text/plain. |
The os and arch values come from uname -s and uname -m. Supported platforms are Linux x86_64/aarch64/arm64 and macOS x86_64/arm64/aarch64. A request for an unsupported platform returns 404 with the list of platforms the latest release publishes.
Install failure modes
The install script fails loudly and leaves any existing install intact when a step before the atomic swap fails.
| Failure | Effect | Recovery |
|---|---|---|
| Download fails (HTTP != 200) | No changes made. | Check connectivity, then re-run. |
| Checksum mismatch | New binary removed; existing install intact. | Re-run the installer. If it persists, the download path may be compromised. |
No SHA256SUMS published |
Warning printed, install continues. | Set ANVIL_INSTALL_REQUIRE_CHECKSUM=1 to make this a hard failure instead. |
| Service install fails after migration | New binary installed but service absent. | Run anvil runner service install manually, unless ANVIL_INSTALL_TOLERATE_SERVICE_FAILURE=1. |
| Post-start verification fails | Service started but the daemon is not active. | Check ~/.anvil-runner/runner.err.log. |
Next steps
- Declare which runner a job needs with
runs_onin .anvil.yml. - See the full CLI in the CLI reference.
- Return to Get started.