Self-hosted runners
A self-hosted runner is your own machine executing runs that hosted Boardwalk schedules. The workflow, its triggers, its run history, and its observability all stay on the platform; the compute, the network position, and the toolchain are yours. It is the same mental model as a CI self-hosted runner, with a Boardwalk-native contract.
Quickstart
An org admin first allows self-hosted runners under Settings > Runners (it is a security toggle, off by default). Then, on the machine, a plain login is enough; your member role must be owner or admin:
npm install -g @boardwalk-labs/cli
boardwalk login
boardwalk runner start --org your-orgThat one command registers the machine into the org's default pool (creating the pool if needed), saves a standing runner identity so restarts skip registration, and goes online. From then on, any workflow that targets the pool runs here:
export const meta = {
slug: "nightly-sync",
triggers: [{ kind: "cron", schedule: "0 6 * * *" }],
runs_on: { kind: "self-hosted" }, // pool defaults to "default"
} satisfies WorkflowMeta;runner start runs in the foreground and executes one run at a time. Start more daemons (or more machines) for concurrency. Useful flags: --pool, --name, --labels gpu,macos, --once (execute one run, then exit), --work-dir, --verbose (daemon debug logs), and --debug (also debug logs inside each run process).
When to use one
Reach for a self-hosted runner when the run needs something hosted runners can't provide: reach into a private network (an internal API, a database behind a VPN, a NAS), special hardware (a GPU box, an Apple Silicon Mac), an OS Boardwalk doesn't host, or data that must stay on machines you control. The run executes locally; run events, logs, and artifacts still stream to the control plane, so the run is observable like any other.
If the requirement is that nothing reaches a hosted control plane at all, you want self-hosting Boardwalk instead: the whole open source engine on your infrastructure. A self-hosted runner is your compute attached to the hosted platform.
Enrolling a fleet
For machines where you don't want an interactive login (servers, lab boxes, provisioning scripts), use the two-step flow: mint a one-time registration token, then redeem it on the machine with the standalone boardwalk-runner daemon (no CLI, no login):
# on your laptop (or from Settings > Runners in the dashboard)
boardwalk runner pools token --org your-org --pool gpu-fleet
# on each machine
npm install -g @boardwalk-labs/runner
boardwalk-runner register --url https://api.boardwalk.sh --token bwkreg_...
boardwalk-runner start --url https://api.boardwalk.sh --pool gpu-fleetThe token is shown once, expires in an hour, is bound to its pool, and registers exactly one machine. The daemon honors HTTPS_PROXY for egress-proxied networks.
Managing runners
Settings > Runners lists every registered machine with its pool, labels, status, and last-seen time, and is where you drain, revoke, or remove one. The same operations exist in the CLI (boardwalk runner list / remove), the REST API, and the MCP server. A runner that stops heartbeating is marked offline; its queued work is re-offered to the rest of the pool. Runs that no runner picks up time out (24 hours by default, configurable per pool) and fail with a clear error rather than waiting forever.
Run isolation
By default each run executes in a throwaway container (Docker or Podman). The run — and the tool calls its agentsteps make — sees only its own workspace and the machine's network, not your home directory, your SSH or cloud credentials, or the rest of the filesystem. The machine's network is preserved, so a containerized run still reaches your LAN, VPN, and localhost services. Full host access is still possible, but only as an explicit mount:
boardwalk runner start --org your-org # containerized (default)
boardwalk runner start --org your-org --mount /data:/data # expose a host path
boardwalk runner start --org your-org --host # no isolation (see below)Container mode needs a container runtime installed and running; if none is found the runner stops with a clear message rather than silently running unisolated. Two more flags tune it: --image (the runner image to run) and --network (defaults to host).
The honest limits on macOS and Windows. Containers there run under a Linux VM, so they isolate Linuxworkloads well but can't run genuinely native work (an Xcode build, native tooling), and reaching a service on the host's own localhost goes through host.docker.internal rather than true host networking. For native macOS/Windows workflows, use --host: the run executes as a normal process with full machine access. That trades isolation for native capability, so reserve it for workflows you trust. Native OS-level sandboxing for macOS and Windows is planned; until then, --host is the answer for native work.
The security model
Registering a machine never hands it the keys to your org. The contract is built on three narrow credentials:
- A registration token (
bwkreg_) enrolls one machine into one pool, once, within an hour. It can do nothing else. - The standing runner token (
bwkr_) can only poll for work, claim it, heartbeat, and deregister itself. It cannot read workflows, secrets, runs, or anything else in the org. - Org reach exists only inside a claimed run: a short-lived run token scoped to that single run, authorized on every call against what the workflow's manifest allows. Secrets resolve through it per run, fail-closed, and are never at rest on the machine.
Work offers carry no credentials at all, programs are digest-verified before they execute, and all control signals (cancel, drain) arrive in heartbeat responses; the platform never connects inbound to your machine. Turning the org's self-hosted toggle off cuts all of it live: registration refuses, deploys refuse, queued work fails fast, and online runners are told to drain.
Private networks
A self-hosted runner needs exactly one network direction: outbound HTTPS to the Boardwalk API. If the machine can reach the internet through a proxy, set HTTPS_PROXY (and NODE_USE_ENV_PROXY=1) when starting the daemon. Runs inherit the machine's network, so anything the box can reach, the run can reach: internal hostnames, VPN routes, tailnets. Boardwalk does not embed a VPN, and doesn't need one; if your machines already sit on a private network (Tailscale, WireGuard, a corporate VPN), runs simply see it.
The inverse case, a hosted runner reaching into your private network, works with standard userspace tunnels: run tailscaled in userspace mode or cloudflared inside the run to join your network for the duration of the run. But if a workflow mostly talks to private services, the simpler answer is usually to run it on a self-hosted runner that is already inside.
Local model endpoints
Bring-your-own inference providersare called directly from the machine executing the run. That means a self-hosted runner can use model servers on its own network, including LAN and localhost endpoints a hosted machine could never reach: an internal vLLM cluster, Ollama on the same box, any OpenAI-compatible server. Configure the provider once in the org (endpoint plus auth secret); reachability is simply the runner's network.
Draining and removal
Ctrl-C drains: the current run finishes, nothing new is claimed, and the daemon exits. A second Ctrl-C force-quits (the platform recovers the interrupted run). Drain in the dashboard does the same remotely via the next heartbeat. Remove (or boardwalk runner remove) deregisters the machine and kills its standing credential immediately; re-enrolling later mints a fresh one.