Skip to main content
The self-hosted server runner is available as a signed standalone Linux binary, an unprivileged container, or a hardened systemd service. It uses runner protocol v2 and reports its engine, plugin runtime, architecture, tags, environment, and concurrency before work can be routed to it. This is the headless runner for servers and NAS devices; it is not a Linux desktop UI.

Install on a Linux server

Download the archive matching uname -m: linux-x86_64 for x86_64, or linux-aarch64 for aarch64/arm64. After verifying the published checksum and provenance, extract and install it:
The installer puts the binary in /usr/local/bin, creates the unprivileged sandbox-runner account and persistent data directory, and installs a hardened systemd unit. Existing configuration is preserved during upgrades, and the service remains stopped until pairing is complete. Complete every placeholder in the config, then validate it as the service account:

Pair the runner

Create a runner in the Operations area and copy the short-lived pairing token. Use it only for pairing: See Runner operations for the account-page workflow and lifecycle controls.
The runner creates an Ed25519 device key locally, sends only the public key with the one-time token, and prints a fingerprint for administrator confirmation. Do not put the token in the config, image, service unit, or shell history.

Start and inspect

Configuration rejects unknown fields. Set both immutable workspace_id and environment_id; the human-readable environment must be development, staging, or production. HTTPS is required except for a localhost development control plane. Working directories and local-network targets are explicit allowlists, and command execution is disabled by default.

Lifecycle

The runner sends health every 30 seconds and polls every two seconds while capacity is available. Draining stops new claims and permits bounded in-flight completion. Revocation invalidates the device key and prevents new claims. Every control-plane command is signature-, target-, expiry-, revision-hash-, permission-, and lease-checked. Command receipts are claimed atomically in runner.sqlite3; a redelivered completed command is acknowledged without executing it again.

Harden production

Use a dedicated unprivileged account, read-only system view/root filesystem, private temporary storage, one approved writable data directory, dropped capabilities, resource limits, explicit mounts, and current plus next command-signing public keys during rotation.

Run on a NAS

On Synology, QNAP, TrueNAS SCALE, Unraid, or another container-oriented host, use the published ghcr.io/sndboxhq/sandbox-server-runner image. Run it as its built-in unprivileged UID/GID 65532, mount the config read-only, and persist /var/lib/sandbox-runner. Mount only the directories workflows are allowed to access. Pair with a one-off container before starting the long-running container; never store the pairing token in Compose or the config. Download and extract either runner archive to obtain config.example.toml, then configure and pair the container:
Replace VERSION with the published immutable version. After confirming the fingerprint, create the long-running container with the same mounts, --restart unless-stopped, --read-only, --cap-drop ALL, and --security-opt no-new-privileges, changing the final command from pair to run. If the NAS does not support containers but provides a normal Linux shell, use the standalone archive and configure its service manager to run /usr/local/bin/sandbox-runner --config /etc/sandbox-runner/config.toml run as the sandbox-runner user.