Runyard Docs
Guides

Installation

Install and deploy a Runyard Hub and runners — from source, with systemd, or with Docker.

Runyard is a self-hosted control plane for agent runs. A deployment has a small, fixed shape:

  • Hub — the HTTP server: API, web UI, MCP-backing endpoints, and the embedded SQLite datastore. One per deployment.
  • Runners — one or more worker processes that register with the Hub using a runner-scoped token, heartbeat, claim queued runs, and execute workflows. A runner can live on the same host as the Hub or on a separate machine.
  • Clients — the runyard CLI, the runyard-mcp MCP server, and the web app are all ordinary API clients of the Hub.

Requirements

RequirementNotes
Node.js >= 22.5The Hub uses the built-in node:sqlite module (started with --experimental-sqlite)
pnpmDependency install and build (corepack enable works)
git, curlFor source installs and the bootstrap script

Runner hosts additionally need the Smithers engine and at least one authenticated agent CLI (claude, codex, or pi) on PATH — see the CLI guide for runyard runner setup.

Quick install (host bootstrap)

The repository ships a one-command, idempotent bootstrap script for a fresh Linux box:

curl -fsSL https://raw.githubusercontent.com/yolo-maxi/runyard/main/install.sh | bash

It ensures Node 22 + pnpm, clones the latest release tag into /opt/runyard (override with RUNYARD_HOME), runs pnpm install --frozen-lockfile, generates a SECRETS_ENC_KEY and session secret into /etc/runyard/runyard.env (never clobbering an existing file, never printing secrets), creates the data directory, installs and enables the systemd units for the Hub and a runner, and installs a daily passive update-check timer (it never auto-applies updates).

When it finishes it prints next steps: sign in with the bootstrap token, mint a runner-scoped token, put it in /etc/runyard/runner.env, and restart the runner unit.

A running Hub also serves its own client install script at https://hub.example.com/install.sh (plus the CLI tarball at /cli.tgz). That one installs the runyard CLI and runyard-mcp on a workstation — see the CLI guide.

Running from source

git clone https://github.com/yolo-maxi/runyard.git
cd runyard
pnpm install
pnpm build          # builds the web app bundle
pnpm start          # node --experimental-sqlite src/server.js

The Hub listens on 127.0.0.1:43117 by default. Start a runner in a second process:

node src/runner.js
# or, via the CLI against a configured remote:
runyard runner start --workspace /path/to/workspace --location local

The runner authenticates with a runner-scoped token (RUNYARD_HUB_TOKEN); on a fresh Hub it may use the bootstrap token until you mint one.

systemd

The repository ships generic units in deploy/. The Hub unit (deploy/runyard.service):

[Unit]
Description=Runyard Hub
After=network.target

[Service]
Type=simple
WorkingDirectory=/opt/runyard
EnvironmentFile=/etc/runyard/runyard.env
ExecStart=/usr/bin/env node --experimental-sqlite /opt/runyard/src/server.js
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target

And the runner unit (deploy/runyard-runner.service):

[Unit]
Description=Runyard Runner
After=network.target

[Service]
Type=simple
WorkingDirectory=/opt/runyard
EnvironmentFile=/etc/runyard/runner.env
Environment=RUNYARD_HUB_URL=https://hub.example.com
Environment="SMITHERS_RUNNER_NAME=vps-runner-1"
Environment=SMITHERS_RUNNER_LOCATION=vps
Environment=SMITHERS_RUNNER_TAGS=linux,node,git,shell,web,smithers
Environment=PATH=/usr/local/bin:/usr/bin:/bin
ExecStart=/usr/bin/env node /opt/runyard/src/runner.js
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target

The bootstrap script installs these for you, retargeted to your install directory, env-file location, and node binary. Secrets live in the EnvironmentFiles (mode 0600), never in the units.

Docker Compose

Runyard publishes two environment-agnostic images to GHCR:

ImageRole
ghcr.io/yolo-maxi/runyard-hubcontrol plane + web UI + embedded datastore (HTTP on PORT, default 8080 in-container)
ghcr.io/yolo-maxi/runyard-runnerclaims runs and executes them; workspace volume at /workspace

The portable base stack is docker-compose.yml at the repo root:

HUB_IMAGE=ghcr.io/yolo-maxi/runyard-hub:0.3.10 \
RUNNER_IMAGE=ghcr.io/yolo-maxi/runyard-runner:0.3.10 \
HUB_PORT=8080 \
docker compose up -d

Provide secrets (RUNYARD_HUB_SESSION_SECRET, SECRETS_ENC_KEY, RUNYARD_HUB_TOKEN, agent API keys) via a .env file or your platform's secret mechanism. For production, pin images by @sha256: digest rather than mutable tags. Optional overlays (all off by default):

  • --profile proxy — a bundled Caddy container that terminates TLS for SITE_ADDRESS and proxies to the Hub. Skip it if you already have a gateway.
  • -f deploy/compose/runner-docker-socket.yml — give the runner docker build access via the host socket (or run a rootless DinD sidecar instead).
  • -f docker-compose.local.yml — dev profile using locally built images.

Confidential VMs (dstack / Intel TDX)

deploy/dstack/ contains an overlay that adapts the base compose for dstack confidential-VM deployments: every image must be pinned by digest (the attestation measures the compose text), the Hub sits on :80 behind the dstack gateway, and updates/rollbacks go through dstack's UpgradeApp with new digests. Render a single self-contained file with:

docker compose -f docker-compose.yml -f deploy/dstack/docker-compose.dstack.yml config > app-compose.yml

See deploy/dstack/README.md in the repository for the full contract.

Reverse proxy

The Hub is a single HTTP listener; any TLS-terminating proxy that forwards all paths to the Hub port works. With Caddy:

hub.example.com {
    reverse_proxy 127.0.0.1:43117
}

Then set BASE_URL=https://hub.example.com in the Hub's env file and restart. An https:// base URL switches the Hub into production session mode. If the proxy is not on loopback, set the trust-proxy configuration accordingly so client IPs (used by rate limiting) are read from X-Forwarded-For only when that header is trustworthy.

Environment variables

Hub (all optional unless noted; legacy SMITHERS_HUB_* names are still read as fallbacks so old deployments keep working):

VariableMeaning
PORTHub HTTP port (default 43117; the container image defaults to 8080)
HOSTBind address (default 127.0.0.1)
BASE_URLPublic URL of the Hub (e.g. https://hub.example.com); an https value enables production behavior
RUNYARD_HUB_DATA_DIRData directory (default <repo>/data) — the DB, artifacts, and bootstrap token live here
RUNYARD_HUB_DBExplicit SQLite database path (default <data dir>/runyard.sqlite)
RUNYARD_HUB_SESSION_SECRETSecret for browser session cookies. Required in production; in dev a generated value is persisted to the data dir
SECRETS_ENC_KEY32-byte key (base64/hex) for the encrypted secrets store. Keep it safe — rotating it makes stored secrets unreadable
RUNYARD_HUB_BOOTSTRAP_TOKENOptional pre-seeded bootstrap admin/runner token (containers); on host installs one is generated into the data dir
RUNYARD_HUB_INSTANCE_NAMEDisplay name of the deployment (default Runyard)

Runner:

VariableMeaning
RUNYARD_HUB_URLHow the runner reaches the Hub (e.g. https://hub.example.com)
RUNYARD_HUB_TOKENRunner-scoped access token minted on the Hub
SMITHERS_WORKSPACEWorkspace directory the runner executes workflows in
SMITHERS_RUNNER_LOCATIONLocation label (vps or local) used for run routing
SMITHERS_RUNNER_NAME / SMITHERS_RUNNER_TAGSRunner display name and capability tags
SMITHERS_RUNNER_CONCURRENCYMax concurrent runs (default 1)
RUNYARD_RUNNER_CHECKOUT_ROOTRunner-owned scratch root for SHA-pinned portable repository checkouts (default <SMITHERS_WORKSPACE>/.runyard-checkouts)
RUNYARD_RUNNER_PORTABLE_CHECKOUTSet to 0 to opt out of portable checkout support; supported runners advertise portable-checkout
RUNYARD_RUNNER_SOURCE_GIT_HOSTSAdditional comma-separated HTTPS hosts that may receive a named repository source credential; defaults to github.com
RUNYARD_RUNNER_REPO_BOX_CANARYSet to 1 on the repo.box trial host to advertise repo-box-canary without joining every general run pool
RUNYARD_RUNNER_SOURCE_ALLOW_FILE_URLSTest-only opt-in for file:/// repository sources in isolated harnesses

Multi-Host Runner Onboarding

Each runner is an outbound-only daemon: it registers, heartbeats, polls, claims, and reports back to the central Hub over HTTPS. Do not open runner-to-runner networking or share working trees between machines.

Create a dedicated service user per host, give it a private SMITHERS_WORKSPACE, a private RUNYARD_RUNNER_CHECKOUT_ROOT, and only the agent credentials that host is allowed to use. Enable RUNNER_SANDBOX=bubblewrap where the host supports user namespaces. Set capacity with SMITHERS_RUNNER_CONCURRENCY; keep the repo.box canary at low capacity with RUNYARD_RUNNER_REPO_BOX_CANARY=1 until it has passed live verification.

Remote-capable repo runs should use repositorySource with provider/repository, full commitSha, and optional targetBranch/baseBranch metadata. The runner checks out that exact commit into a per-run directory, verifies git rev-parse HEAD, injects the local checkout path as repoDir, and deletes the checkout on success, failure, cancellation, or pause. Branch creation and worktrees start from the pinned SHA; pushes stay explicit workflow actions.

For private repositories, prefer GitHub App/JIT credentials where the workflow already supports them. Otherwise store a narrowly scoped read token as a named Hub secret and reference it with credentialSecretName; tokens must never be embedded in clone URLs or durable run input. Public repositories require no credential.

To drain a host before maintenance, write the normal drain flag through the updater/ops path or stop the systemd unit after active runs finish. To promote repo.box, remove the canary-only routing constraint and add the normal shared pool tags in SMITHERS_RUNNER_TAGS after verification; no code deploy or runner-to-runner wiring is needed.

Where state lives

Everything durable is in the Hub's data directory (RUNYARD_HUB_DATA_DIR, default data/; the hub-data volume mounted at /data in containers). It survives every update and rollback:

  • runyard.sqlite — the SQLite database (runs, workflows, tokens, schedules, approvals, encrypted secrets)
  • artifacts/ — uploaded run artifacts
  • bootstrap-token.txt — the first admin token, generated on first boot (mode 0600); use it to sign in once and mint scoped tokens
  • session-secret.txt — the persisted dev session secret when none is provided

Back this directory up. The runner workspace, by contrast, is disposable scratch space — outputs and event traces are uploaded to the Hub as artifacts.

Next steps

On this page