Set up a runner securely
This guide adds a runner — a separate machine that pulls and executes agent tasks — to a SupaCloud install. The runner connects outbound-only over HTTPS, so it works behind NAT with no inbound ports. For the concepts (pull transport, the per-run secret model, the two meanings of “runner fleet”) see the explanation The runner fleet.
1. Turn hub mode on (server)
Section titled “1. Turn hub mode on (server)”-
Set one environment variable on the server and restart it:
SUPACLOUD_HUB_MODE=trueThe default is
false. While off, every task runs locally regardless of how many runners are registered — which is what makes hub mode safe to flip off in a hurry without de-registering anything. -
Confirm it is on:
Terminal window curl https://supacloud.example.com/api/runners/config \-H "Authorization: Bearer <admin-session-or-API-token>"# → { "hub_mode_enabled": true }
2. Register the worker node (server)
Section titled “2. Register the worker node (server)”-
As a system admin, register the runner and capture the token:
Terminal window curl -X POST https://supacloud.example.com/api/runners \-H "Authorization: Bearer <admin-session-or-API-token>" \-H "Content-Type: application/json" \-d '{"name": "gpu-worker-01","capabilities": { "images": ["unified"], "max_parallel": 2 }}' -
The response includes the plaintext bearer token (
scrn_…) exactly once — the server stores only its SHA-256 hash. Copy it now; you will hand it to the worker in the next step. If you lose it, rotate the token rather than re-registering.
Advertise Wasm connector and script execution
Section titled “Advertise Wasm connector and script execution”A runner can also opt in to running Wasm on its node — both marketplace connector nodes and free-form scripts (JS / TS / Python). Both run inside the same WASI 0.2 sandbox (per-run memory + wall-clock caps, deny-all SSRF-vetted egress) behind the runner’s own OS boundary. Two capability keys gate it:
connectors: true— the runner opts in to running connector-node Wasm. The hub never dispatches a connector run to a node without this flag.wasm.script_languages— the script languages whose interpreter the runner’s image actually provisioned. The hub dispatches a script run only to a runner that advertises bothconnectors: trueand the script’s language here (a script runs Wasm on the node, so the node must have opted into Wasm execution and have the interpreter present).
{ "name": "wasm-worker-01", "capabilities": { "images": ["unified"], "connectors": true, "wasm": { "script_languages": ["js", "ts", "py"] } }}List only languages whose interpreter the runner has actually provisioned
under SUPACLOUD_SCRIPT_RUNTIME_DIR (built reproducibly by
scripts/build-script-runtimes.sh). The matcher is fail-closed: an absent or
malformed wasm key means the runner advertises no script language and is never
sent a script — so a pre-existing fleet is wholly unaffected, and adding the key
is a safe, monotonic opt-in.
When a script is dispatched to the fleet the hub ships metadata only — the language plus the source and inputs as data — and a SHA-256 integrity anchor of its own interpreter. It never ships the multi-MB interpreter. The runner resolves its own local, trusted interpreter for that language and re-checks the SHA before running; a mismatch (version skew or tamper) fails closed. A request that tried to smuggle interpreter bytes is rejected as a protocol violation. The interpreter is always a runner-local trusted asset — a payload-shipped one is never executed.
This is governed instance-wide by SUPACLOUD_CONNECTOR_EXECUTOR_MODE
(in_process — the default — / pooled / runner_fleet); set it to
runner_fleet to dispatch connector and script runs to the fleet. With the
default in_process mode, scripts run on the hub exactly as before — this is a
zero-config no-op for existing deployments.
Onboard a third-party runner (enrollment)
Section titled “Onboard a third-party runner (enrollment)”Section 2 mints a long-lived scrn_… bearer for a runner you operate. To
bring up a runner on a host you trust less — a customer’s machine, edge compute,
a partner’s GPU box — use the enrollment flow instead. You hand the operator
a single-use, short-lived enrollment token; their runner exchanges it on boot
for a scoped, short-lived scrnj_… JWT and refreshes it automatically. The
operator never holds a long-lived hub credential, and the runner can only ever
serve the workspaces you scoped it to. For the trust model behind this, see
Runner-fleet security.
-
Mint the enrollment token (admin). Choose the trust class, execution tier, isolation and the workspaces the runner may serve:
Terminal window curl -X POST https://supacloud.example.com/api/runners/enroll \-H "Authorization: Bearer <admin-session-or-API-token>" \-H "Content-Type: application/json" \-d '{"class": "edge","tier": "docker","isolation": "container","allowed_workspace_ids": ["<workspace-uuid>"],"allowed_labels": []}'classistrustedoredge;tieriswasm,dockerorboth;isolationiscontainerormicrovm. The response carries the plaintext enrollment token exactly once (the server stores only its hash) and a short expiry. Copy it now and hand it to the operator over a secure channel. -
Onboard the worker with the installer (operator). The operator runs the hosted one-line installer, which downloads the signed runner artifact, verifies its checksum and signature, exchanges the one-time enrollment token for a scoped
scrnj_…JWT, and boots the runner with that JWT — outbound-only, no inbound ports:Terminal window curl -fsSL https://supacloud.example.com/install-runner.sh | \SUPACLOUD_HUB_URL=https://supacloud.example.com \SUPACLOUD_RUNNER_ENROLLMENT_TOKEN=<one-time-token> \SUPACLOUD_RUNNER_NAME=customer-edge-01 \bashThe installer (not the bare binary) performs the exchange: the standalone
supacloud --runnerbinary only ever readsSUPACLOUD_RUNNER_TOKEN(thescrnj_…JWT), never the enrollment token. To onboard without the installer, exchange the token yourself — theRunnerService.ExchangeTokengRPC RPC ({enrollment_token, name}→ the JWT, ADR 0050) — then setSUPACLOUD_RUNNER_TOKEN=<the scrnj_ JWT>and run the binary or image exactly as in section 3. -
The runner refreshes its JWT automatically. The exchange burns the enrollment token (it cannot be replayed) and yields a scoped
scrnj_…JWT carrying the runner’s trust class, tier, isolation and allowed workspaces. The JWT lives at most one hour; the runner daemon re-fetches a fresh one before it expires — by default at roughly half the token TTL, derived from the grant’stoken_expires_at/ the JWTexp— and each refresh calls the hub’s grant-recheckingRunnerService.RefreshTokengRPC RPC, so revoking a workspace takes effect within one token lifetime. To pin the cadence explicitly, setSUPACLOUD_RUNNER_TOKEN_REFRESH_SECSto the number of seconds-before-expiry at which to re-exchange (0, the default, means derive it).
3. Bring the runner online (worker node)
Section titled “3. Bring the runner online (worker node)”The runner is the same SupaCloud binary run in --runner mode. Pick the
platform you are installing on. In every case the configuration is the same set
of environment variables; only how you launch differs.
One-line installers (signed binary)
Section titled “One-line installers (signed binary)”Once your release channel hosts the signed binaries, the installers do the whole
onboarding for you on a worker node — detect the platform, download the pinned
binary, verify the checksum and signature, exchange the one-time enrollment
token, install, and start supacloud --runner over TLS with no inbound ports.
The only secret you supply is the single-use enrollment token from the mint step;
the script body embeds none.
Linux / macOS (the exact one-liner the SupaCloud UI hands you):
curl -fsSL "$SUPACLOUD_URL/install-runner.sh" \ | SUPACLOUD_ENROLLMENT_TOKEN=<one-time-token> bashWindows (PowerShell):
$env:SUPACLOUD_URL = "https://supacloud.example.com"$env:SUPACLOUD_ENROLLMENT_TOKEN = "<one-time-token>"irm "$env:SUPACLOUD_URL/install-runner.ps1" | iexUseful overrides (env vars, both installers): SUPACLOUD_RUNNER_NAME (defaults to
the hostname), SUPACLOUD_RUNNER_VERSION / SUPACLOUD_DOWNLOAD_BASE (pin a
specific release or artifact host), and SUPACLOUD_RUNNER_DRY_RUN=1 to print the
resolved download + verify + start plan without touching the network.
Common configuration (all platforms)
Section titled “Common configuration (all platforms)”SUPACLOUD_HUB_URL=https://supacloud.example.comSUPACLOUD_RUNNER_TOKEN=scrn_xxxxxxxxxxxxxxxxxxxxxxxxSUPACLOUD_RUNNER_NAME=gpu-worker-01# Optional cadence overrides (sensible defaults shown):# SUPACLOUD_RUNNER_HEARTBEAT_SECS=30# SUPACLOUD_RUNNER_POLL_SECS=3# Docker network the runner puts agent containers on (must exist on the worker):# AGENT_NETWORK=supacloud-agentsThe hub URL must be https://. A plaintext http:// hub is refused at boot
unless you explicitly set SUPACLOUD_RUNNER_ALLOW_INSECURE_TRANSPORT=true — only
ever acceptable on a trusted private network.
Linux / macOS
Section titled “Linux / macOS”-
Put the variables above in a
.envnext to the binary (or export them), then:Terminal window supacloud --runner -
Or with the Docker image (mount the Docker socket so the runner can launch agent containers on its own host):
Terminal window docker run -d --name supacloud-runner \-e SUPACLOUD_HUB_URL=https://supacloud.example.com \-e SUPACLOUD_RUNNER_TOKEN=scrn_xxxxxxxxxxxxxxxxxxxxxxxx \-e SUPACLOUD_RUNNER_NAME=gpu-worker-01 \-v /var/run/docker.sock:/var/run/docker.sock \<your-supacloud-image> --runner
Windows
Section titled “Windows”-
The runner needs a Docker host to launch agent containers — install Docker Desktop (WSL 2 backend) on the worker.
-
In PowerShell, set the variables for the session and launch the binary:
Terminal window $env:SUPACLOUD_HUB_URL = "https://supacloud.example.com"$env:SUPACLOUD_RUNNER_TOKEN = "scrn_xxxxxxxxxxxxxxxxxxxxxxxx"$env:SUPACLOUD_RUNNER_NAME = "win-worker-01".\supacloud.exe --runnerFor an always-on worker, run the same command as a service (e.g. with NSSM) or run the Docker image command from the Linux/macOS tab inside WSL 2.
microVM execution backend (strongest isolation)
Section titled “microVM execution backend (strongest isolation)”The default docker backend runs each task in a container on the runner’s local
Docker host. For an untrusted or edge runner, the microVM backend is the
strongest isolation tier: each claimed task runs in its own short-lived
hardware-virtualised guest (a Firecracker/Kata-class microVM), so a compromised
agent process is contained behind a VM boundary, not just a kernel namespace. For
how the tiers compare, see Runner-fleet defense-in-depth →
isolation tiers.
-
Build (or obtain) a runner binary compiled with the
microvm-backendfeature. -
Provision a microVM launcher on the worker (e.g. a Firecracker/Kata wrapper), then select the backend and point the runner at it:
SUPACLOUD_RUNNER_EXECUTION_BACKEND=microvmSUPACLOUD_RUNNER_MICROVM_CMD=/usr/local/bin/launch-microvm -
Enroll (or re-enroll) the runner with
isolation: microvmso the hub’s fail-closed dispatch filter routes microVM-required tasks to it and only it.
Secure the runner transport with mTLS
Section titled “Secure the runner transport with mTLS”By default the runner trusts the hub over one-way TLS (the hub presents a server certificate; the runner verifies it against the system trust store). To require a mutual TLS handshake — so the hub also authenticates each runner by client certificate, a strong second factor on top of the bearer token — configure both ends. mTLS is optional; with none of these set, transport is exactly as today.
-
On the hub (server). Serve TLS directly and require client certificates:
SUPACLOUD_TLS_CERT_PATH=/etc/supacloud/tls/hub.crtSUPACLOUD_TLS_KEY_PATH=/etc/supacloud/tls/hub.keySUPACLOUD_TLS_CLIENT_CA_PATH=/etc/supacloud/tls/runner-ca.crtSUPACLOUD_TLS_CERT_PATH+SUPACLOUD_TLS_KEY_PATHturn on TLS;SUPACLOUD_TLS_CLIENT_CA_PATHadditionally requires and verifies a client cert against that CA. A connection without a trusted client cert is rejected at the handshake. -
On each runner. Present a client identity and (optionally) pin the hub CA:
SUPACLOUD_RUNNER_TLS_CLIENT_CERT_PATH=/etc/supacloud/tls/runner.crtSUPACLOUD_RUNNER_TLS_CLIENT_KEY_PATH=/etc/supacloud/tls/runner.keySUPACLOUD_RUNNER_TLS_CA_PATH=/etc/supacloud/tls/hub-ca.crt # optional: pin the hub CA
4. Verify
Section titled “4. Verify”-
On start the runner makes one heartbeat call. If the token is rejected it exits immediately with a clear error; if the hub is momentarily unreachable it starts anyway and keeps retrying.
-
The runner’s row flips
pending → onlineon its first successful heartbeat. Confirm from the server:Terminal window curl https://supacloud.example.com/api/runners \-H "Authorization: Bearer <admin-session-or-API-token>" -
Launch a task whose image the runner advertises. With hub mode on and a matching online runner, the server dispatches it to the runner and the live event stream shows progress exactly as for a local run.