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_on labels.
  • Every label in the job’s runs_on is 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