Connect a runner
A runner is the machine where workspaces live: their files, containers, repositories, terminals and agents. It can be a server, a cloud VM or the computer under your desk. A workspace is placed on one runner when it is created.
A runner connects out to Yard over one connection. It needs no open port, no public IP address and no certificate.
Requirements
| System | macOS, Linux (Ubuntu or Debian recommended) or Windows (experimental) |
| Docker | Docker Desktop (macOS, Windows) or Docker Engine 24 or newer (Linux), running. On Linux the installer offers to install it. |
| Account | Your own user account is enough. As root on Linux, each workspace also gets its own system user. |
| Disk | Room for your repositories and their checkouts, plus about 4 GB for the workspace image |
| GPU (optional) | An NVIDIA GPU with its driver and nvidia-container-toolkit (Linux) |
Every workspace runs in its own Docker container, so agents, terminals and code from repositories never run directly on the machine.
Register a runner
Organization admins register runners:
- Open Runners in the organization's sidebar and choose + Register runner.
- Give it a name.
- Pick how to install it: macOS / Linux, Windows, Compose, or
Installed (for a machine that already has the
yardCLI). - Follow the numbered steps the dialog shows for that platform (Docker, the command, and for Compose the file and its variables), each with its own copy button.
The command carries the runner's token. It is shown only once, so run it now or keep it somewhere safe. The last step follows the machine: it says Token checked as soon as the installer has checked the token, and Connected once the runner is online and new workspaces can use it. When the token was checked but the runner is still not online after ten minutes, it says what to run on the machine.
Install and connect
macOS and Linux
curl -fsSL https://api.useyard.app/install.sh | sh -s -- --token <token>
Run it as the account that will run the runner, in a real login (SSH, or
su - <user> with the dash). That account must be able to run docker version. See Running as a dedicated account.
The script installs the yard CLI (see Install the yard CLI).
When its folder is not on your PATH it asks whether to add it to your
shell's profile (Enter adds it, once; --yes adds it without asking). When
another, older yard comes first on your PATH it names it and how to
remove it. Then it runs yard runner up, the guided setup. It goes through
numbered steps, asks only for what is missing, and marks each step done or
failed:
Downloading yard 0.12.2 for linux-x64…
Installed /usr/local/bin/yard
Setting up a Yard runner · yard 0.12.2 (/usr/local/bin/yard) · root@server
1. Data directory ✓ /var/lib/yard
2. Token ✓ checked and saved (runner rnr_…)
3. Mode ✓ docker: one container per workspace
4. Docker ✓ docker 29.0.1 (installed just now)
5. Box image ✓ yard-box:latest built
6. Service ✓ yard-runner installed, enabled at boot, started
7. Online … waiting for the runner to connect
7. Online ✓ connected to https://api.useyard.app as rnr_…
Setup complete. This runner shows as online under Yard → Runners.
Every question says what Enter does, for example Run as a background service, started at boot? (Enter for yes, n for no).
- Data directory. Where the runner keeps its settings and workspaces:
/var/lib/yardas root on Linux,~/.yardotherwise. It is not asked for unless that cannot be used; give--data-dirfor another one. A directory with other files in it is refused. If this machine already has a runner, see Several runners on one machine. - Token. Checked with Yard and saved straight away, before anything
that can fail. A token Yard refuses fails here, in seconds. Without
--tokenthe setup asks for it (what you type is not shown). - Mode. Asked once (press Enter for Docker; see Runner modes).
- Docker. When Docker is missing on Linux and the script runs as root,
it asks whether to install Docker Engine with Docker's own install script
(
get.docker.com). Press Enter to install it; the setup then enables and starts it and carries on. A stopped Docker is offeredsystemctl enable --now docker. As an ordinary user it prints the commands to run instead, for your account by name, "as root" or withsudowhen sudo works for you. It tells apart a Docker that is not installed, one whose daemon is not running, adockerthat could not be started at all, an account not in thedockergroup, and one added to the group after this shell started (log in again:su - <user>, a new SSH session ornewgrp docker). Anything else is shown as Docker said it. On macOS and Windows, install and start Docker Desktop first. - Box image. Builds the workspace image (a few minutes the first time).
- Service. Installs the background service, enables it at login or
boot, and starts it. For an ordinary account on Linux it first turns on
lingering (so the service runs without a login) and finds the account's
systemd user session, also from a shell that came from
su. - Online. Waits until the runner is connected to Yard. Only then does
it print
Setup complete.If the service stops (for example because the token was refused) or does not connect within 90 seconds, it prints why, the runner's last log lines and what to run, and exits with an error.--verboseshows the log lines as they come. Ctrl-C while it waits stops only the waiting: the service keeps running.
Carrying on. When a step fails, the last line starts with Next: and
says what to do, which usually ends in yard runner up. The token, mode and data directory are
saved, so it is not asked for again and the steps already done are quick.
Script options, after sh -s --:
| Option | What it does |
|---|---|
--token <token> |
The runner's token (or set YARD_TOKEN). |
--api <url> |
Connect to another API than the one the script came from. |
--dir <path> |
Where to put yard (default: ~/.yard/bin, or /usr/local/bin as root). |
--data-dir <path> |
Where the runner keeps its data (or set YARD_DATA_DIR). A new or empty directory, or one a runner already uses. |
--mode docker or --mode direct |
Choose the mode up front instead of being asked. |
--install-docker |
Linux, as root: install Docker Engine without asking when it is missing. For unattended installs. |
--no-install-docker |
Never offer to install Docker. |
--yes |
Take the defaults without asking: add yard's folder to PATH in your shell's profile, and with --mode direct skip the confirmation. |
--no-service |
Run the runner in this terminal instead of installing a service. |
--wait <seconds> |
How long to wait for the runner to come online (default 90). |
--replace |
Another runner uses the data directory: move it to this token (see below). |
--insecure |
Allow a plain http:// API address. |
Without a terminal to ask on (a script, CI) and without --install-docker,
a missing Docker is not installed: the setup stops and says so. The script
exits with the setup's exit code, so a failed setup fails curl … | sh.
Running the script again with the same token updates yard and
restarts the runner. With another token on a machine that already has a
runner, it asks what to do; see the next section. It never takes over a
runner without asking.
Windows
In PowerShell, with Docker Desktop running:
$env:YARD_TOKEN = "<token>"; irm https://api.useyard.app/install.ps1 | iex
Workspaces run as Linux containers under Docker Desktop. There is no background service on Windows: the runner runs in that PowerShell window. Windows support is experimental.
A machine that already has yard
yard runner up --token <token>
Several runners on one machine
One machine can run several runners, for example one per organization. Each has its own data directory and its own background service.
When you run the install script (or yard runner up) with a new token on a
machine that already has a runner, the setup says so and asks:
This machine already has a runner in /var/lib/yard
runner rnr_… · connected to https://api.useyard.app · service yard-runner (running)
What should this token do?
1) Add a second runner next to it, in /var/lib/yard/runner-2 (recommended)
2) Replace that runner: move its service to this token (its workspaces stay in /var/lib/yard)
Choose 1 or 2 [1]:
- 1 leaves the first runner and its service as they are, and sets up
the new one in the first free
runner-Nfolder, with a service of its own. - 2 moves the existing service to the new token. The workspaces in that
directory belong to the old runner, so the setup first asks you to release
them (
yard runner service stop, thenyard runner adopt-data-dir --yes), and then to run it again with--replace. Only do this when the old runner is gone for good.
Without a terminal to ask on, nothing is changed and both commands are printed. To choose up front, give the new runner its own directory:
curl -fsSL https://api.useyard.app/install.sh | sh -s -- --token <token> --data-dir /var/lib/yard/org-b
Services. The runner in the default data directory keeps the service
name every install had: yard-runner (systemd) or app.useyard.runner
(launchd). Every other data directory gets its own, named after its folder:
/var/lib/yard/runner-2 runs as yard-runner-runner-2 /
app.useyard.runner.runner-2.
Managing each one. Every yard runner command takes --data-dir to
pick the runner; without it, it means the one in the default directory:
yard runner list # every runner on this machine, its data directory and state
yard runner service status --data-dir /var/lib/yard/runner-2
yard runner service logs -f --data-dir /var/lib/yard/runner-2
yard runner config show --data-dir /var/lib/yard/runner-2
yard update updates yard once and restarts every runner service on the
machine.
yard runner up
yard runner up is the guided setup above. Run it again at any time: it
uses what was saved and only does what is still missing.
| Flag | What it does |
|---|---|
--token <token> |
Connect the runner with this token. Without it, the saved one is used, or the setup asks for it. |
--api <url> |
With --token: the API to connect to, for a self-hosted Yard. Default: https://api.useyard.app. |
--data-dir <path> |
The runner's data directory (see above). |
--service / --no-service |
Install the background service, or run in this terminal. Without either, a terminal is asked (Enter: the service); a run without a terminal stays in the foreground, as in a container. |
--mode docker or --mode direct |
How workspaces run. Asked the first time when not given; saved for later. |
--install-docker / --no-install-docker |
Install Docker Engine without asking (Linux, root), or never offer it. |
--wait <seconds> |
How long the Online step waits (default 90). |
--replace |
Move the runner in this data directory to the new token. |
--yes |
Take the defaults; with --mode direct, skip the confirmation. |
--rebuild |
Build the workspace image again. |
--no-gpu |
Do not build the GPU image, even when the machine has a GPU. |
--insecure |
Allow a plain http:// API address. The token would travel unencrypted. |
Without the service the runner runs in the terminal until you press Ctrl-C;
Setup complete. is printed once it connects.
One runner runs per data directory. If one started in a terminal is
already running there, up says so, with its process id and how to see or
stop it, and does nothing.
Runner modes
- Docker (default, recommended): every workspace runs in its own container, isolated from the machine.
- Direct (discouraged): no containers. Agents, terminals and everything
a repository runs become ordinary processes on the machine, with access to
its network, devices and, outside root on Linux, your own files. Use it
only on a machine that cannot run Docker. The runner explains what it
gives up and asks you to type
directto confirm. Direct mode needsgit,python3,bashandtaron the machine, and the agent CLIs you want to use (claude,codex,cursor-agent).
yard runner config set mode docker switches a direct runner back to Docker.
Running as a service
On macOS (launchd) and Linux (systemd), the runner can run in the background
and start again after a crash, at login or at boot. yard runner up --service and the install script set this up; to do it yourself:
yard runner service install
| Command | What it does |
|---|---|
yard runner service install |
Install the service, start it and wait until the runner is online. Refused while the runner is not enrolled. |
yard runner service uninstall |
Remove the service. |
yard runner service status |
Whether it is installed and running, since when, whether it is connected to Yard, and its last log lines. |
yard runner service start |
Start it, and wait until the runner is online. |
yard runner service stop |
Stop it. It starts again at the next login or boot. |
yard runner service restart |
Restart it, and wait until the runner is online. |
yard runner service logs |
Show its log. -f follows it; -n <lines> sets how many lines (default 50). |
install, start and restart wait, and fail with the reason and the
runner's last log lines when it does not come online; --no-wait
returns at once. Every command takes --data-dir to pick the runner (see
Several runners on one machine).
service status takes --json for the same as JSON, and -n <lines> for
how many log lines to show (default 10). It exits with 0 when the service
runs and 3 when it does not.
As root on Linux the service is system-wide; otherwise it belongs to your user. On macOS the runner keeps the Mac from going to sleep while idle, because a sleeping Mac takes its workspaces offline. Closing the lid still puts it to sleep; the runner reconnects when it wakes.
Running as a dedicated account
On a Linux server a runner can run as an account of its own instead of root, say one per organization. As root, once:
useradd -m runner-nps # the account
usermod -aG docker runner-nps # it may use Docker
loginctl enable-linger runner-nps # its services run from boot, with no login
Then log in as that account over SSH, with machinectl shell runner-nps@,
or with su - runner-nps (with the dash), check that id lists docker
and docker version shows a server version, and run the install command
there.
A plain su runner-nps (without the dash) keeps root's directory and
PATH. The setup works around the directory, and warns when an older
yard comes first on PATH. A shell from su also has no systemd user
session; the setup finds the account's session by itself once lingering is
on. When it is off and the account may not turn it on, the setup says so
before it asks for the token, and its last line is the command root runs:
loginctl enable-linger runner-nps.
To run the runner in a terminal instead, without a service:
yard runner start
Change settings
yard runner config shows and changes a runner's settings without running
the whole setup again. With --data-dir it addresses that runner.
yard runner config show
data directory /var/lib/yard (default)
api https://api.useyard.app (saved)
token yrn_…9f2c, confirmed 2026-10-05 (saved)
runner rnr_…
mode docker (saved)
service yard-runner (running, enabled at boot)
Each value says where it comes from: saved, the environment, or the
default. The token is only shown by its first and last characters.
--json prints the same for scripts.
| Command | What it does |
|---|---|
yard runner config set token <token> |
Checks the new token with Yard first and saves it only when Yard accepts it; a refused token leaves the old one. Without a value it asks (hidden). |
yard runner config set mode docker |
Checks Docker (and offers to install it, as in the setup), builds the workspace image, then switches. |
yard runner config set mode direct |
Asks you to confirm, as the setup does (--yes skips it). |
yard runner config set api <url> |
Another API, for a self-hosted Yard. The token is checked against it first. |
yard runner config set data-dir <path> |
Points the service at another data directory that already has a runner. Nothing is moved; the old service is removed. For a new directory, set a runner up there with yard runner up --data-dir <path>. |
After a change the service is restarted and followed until the runner is
online. --no-restart only saves; without a service, the change applies at
the next yard runner start. A mode change is refused while workspaces run
on the runner. A setting that the runner's environment sets (RUNNER_TOKEN,
RUNNER_MODE, RUNNER_API_URL, for example in a compose file) wins over
the saved one: set says so and tells you to change it there.
Checking a runner
yard runner status
Shows where the runner's settings are, which API it is connected to,
whether its token is confirmed (token saved, not confirmed yet until it
first connects), the mode, whether it is running (and whether by the
service or in a terminal), and whether a newer build exists.
yard runner doctor
Shows what this machine offers: the mode, whether the workspace image is there, whether the runner is connected to Yard, the GPU, and the direct-address settings. Use it when a runner does not behave as expected.
Both print JSON.
Updating
A runner checks for a newer build when it starts and every few hours. When
there is one, yard runner status and the Runners page say so. To update:
yard update
This downloads the newest build from the API the runner is connected to,
checks it, replaces yard and restarts every runner service on the
machine. yard update --check
only says whether there is one.
Organization admins can also choose Update next to an online runner on
the Runners page. If a terminal command or an agent turn is running on it,
Yard asks you to try again once it is idle. Runners
too old for that button need one yard update on the machine first.
To have a runner install new builds by itself, between jobs, set
RUNNER_AUTO_UPDATE=1 in its environment before installing the service.
Docker Compose
On a Linux server with Docker Engine, or a platform such as Coolify or
Portainer, the runner can itself be a container. In Register runner,
choose Compose: copy the compose file and set the two variables shown,
RUNNER_API_URL and RUNNER_TOKEN. The API also serves the file at
https://api.useyard.app/runner-compose.yml.
docker compose -f runner-compose.yml up -d
Things to know:
- Workspaces still get one container each. The runner starts them through the host's Docker socket, which is mounted into the runner's container only.
- Workspace files live in
/var/lib/yardon the host. That path must be the same inside and outside the container. - Restarting the container fetches the newest runner build, and the file
sets
RUNNER_AUTO_UPDATE=1, so it also updates itself between jobs. - The file mounts the host's
/etc/machine-id, so a recreated container still counts as the same machine (see below).
The runner's machine
A runner is pinned to the machine it first connects from. A copy of its token alone is not enough to receive workspace files or agent work on another machine.
When the runner's token is used from another machine (a different computer, or the runner's data folder copied elsewhere), that connection is held: connected, but given no work. The organization's admins are notified, and the Runners page shows that the runner connected from another machine:
- Approve this machine pins the runner to the new machine. It goes live straight away, without reconnecting.
- Reject closes the connection and refuses that machine from then on. Rotate the token too.
Moving a runner to another machine
Open the runner's Settings on the Runners page and choose Reset device. The runner forgets its machine, and the next machine that connects with its token is pinned without asking. Then install the runner on the new machine with the same token, or rotate the token first and use the new one.
Rotating the token keeps the pin: the pin belongs to the runner, not the token. Give every machine its own runner; two machines cannot share one.
Rotating the token
In the runner's Settings, Rotate token gives it a new token and revokes the old one. An online runner receives the new token over its connection and stays connected; nothing has to be done on the machine. An offline runner is shown the command to reconnect it with the new token.
A runner's own address
By default, terminals and file downloads travel through Yard's API. A runner can also serve them to browsers directly, on an https address of its own, which is faster when the runner is far from the API.
-
On the machine, choose where the runner listens, then restart it:
# behind your own reverse proxy or tunnel that serves https yard runner direct set --listen 127.0.0.1:7443 # or with your own certificate yard runner direct set --listen 0.0.0.0:443 --cert cert.pem --key key.pem yard runner service restart -
On the Runners page, open the runner's Settings and enter the address under Own address, for example
https://runner-1.example.com. Yard checks that the address answers as this runner, and checks again every hour. Only a pinned runner can have an address.
--cert and --key go together. Without them, something in front of the
runner must serve https.
When the address cannot be reached from a browser, that browser goes through the API instead. A terminal shows "direct" while it is connected directly. Uploads, screenshots and the live view always go through the API.
To stop:
yard runner direct off
yard runner service restart
Then remove the address in the runner's Settings.
Settings
The runner reads a few settings from its environment. Set them before
yard runner up --service or yard runner service install, which copy
them into the service.
| Variable | Default | What it does |
|---|---|---|
RUNNER_DATA_DIR |
~/.yard, or /var/lib/yard as root on Linux |
Where the runner keeps its settings and workspaces; the same as --data-dir. The first runner to connect from it owns it; any other runner refuses to start there (yard runner adopt-data-dir --yes hands it over). Never share one between two runners. |
RUNNER_BOX_IDLE_MINUTES |
30 |
Stop a workspace container nobody has used for this long (0 = never). |
RUNNER_AGENT_CONCURRENCY |
4 |
How many agent turns may run at once. Agents at once in the runner's settings in the app takes precedence. |
When the machine is busy
In the runner's settings in the app, Load says when new agent turns wait
for the machine: above the CPU threshold or the memory threshold (90% each
by default), new turns wait in the runner's queue and start one at a time
once both are 10 points below. Turns already running finish. A spike of a
few seconds does nothing. When the load is critical, the runner also pauses
workspaces nobody is using and starts them again once it is back to normal;
a workspace someone stopped stays stopped. Changes apply at once, without a
restart. The same dialog lists the runner's queue, with Retry all
failed. Organization admins, instance admins and the owner of a personal
runner can change these settings. Runners before 0.12.0 have no load gate:
update them first.
| RUNNER_TRASH_HOURS | 24 | How long a deleted project's files are kept on the machine (0 = keep). |
| RUNNER_ARCHIVE_DAYS | 30 | How long an archived workspace's local copy is kept (0 = keep). |
| RUNNER_KEEP_AWAKE | on | macOS: keep the machine from idle sleep. 0 turns it off. |
| RUNNER_AUTO_UPDATE | off | Install newer builds by itself, between jobs. |