Workers and tunnels
A worker is a machine that runs sandboxes. It joins by dialing out, so it never needs an inbound port, a public address, or a firewall exception.
Joining
# on the control plane: mint a join token for one workspace
curl -s -X POST "$API/portal/v1/join-tokens" \
-H "X-Sandrpod-Token: $TOKEN" -H 'Content-Type: application/json' \
-d '{"name":"worker-1"}'{"secret":"jn_…","command":"docker run -d --name sandrpod-worker …"}Run that command on the new machine. It starts poder with the token, which dials -api-url and registers.
| Flag | Meaning |
|---|---|
-api-url | the control plane to dial |
-token | the join token (jn_…) |
-region | which region this worker serves; must match what clients ask for |
-network | Docker network for sandbox containers (default bridge) |
-max-containers | how many sandboxes this machine will hold |
Region is the field that bites. The CLI and the E2B surface both default to local; a worker registered with -region=hk is invisible to them, and creation fails with no available local poder found while the fleet looks healthy.
What a worker advertises
At handshake the worker sends its identity, resources, capabilities, and tenant:
| Field | Effect |
|---|---|
X-Poder-Capabilities: egress | only this worker may host a sandbox that asks for an egress policy |
X-Poder-Workspace: … | placement scoped: a sandbox from workspace A is not scheduled onto workspace B's worker |
| resources (arch, OS, cores, memory) | recorded on the sandbox so the API can report it |
A sandbox that asks for a policy on a fleet with no capable worker gets 409, not a sandbox that silently runs unwalled.
Heartbeats and state
Workers heartbeat every ten seconds with their container list. That does three things: keeps the record ONLINE, lets the control plane reconcile sandbox states against the containers that actually exist, and (in a multi-instance deployment) renews which node holds the tunnel.
A worker that stops heartbeating goes OFFLINE; its sandboxes stop being reachable, and a create that lands on it fails with a reason rather than hanging.
Prerequisites on the machine
| Requirement | Why |
|---|---|
| Docker socket | the worker creates containers |
SANDRPOD_TOOLBOX_IMAGE | the sandbox base image, pulled on first use |
ghcr.io/sandrpod/egress-proxy pre-pulled | only for sandboxes with an egress policy; without it creation fails after the record is written |
| Ports: none inbound; outbound HTTPS + DNS | the worker dials the control plane and pulls images |
Disk is the resource that fills first: images plus per-sandbox writable layers. A 4 vCPU / 8 GB / 100 GB machine carries several concurrent sandboxes; the default sandbox is 1 core and 1 GiB.
Pausing versus deleting
Stopping a sandbox pauses the container; it still occupies a container slot on the worker until it is deleted. A worker at its max-containers limit refuses new placement (no available poder found), which is a capacity answer, not a bug.
Seeing the fleet
sandrpod-cli poder list # id, state, provider, arch, region
curl -s "$API/api/v1/poders" -H "X-Sandrpod-Token: $TOKEN" | jqpoder delete <id> removes the record and drops the tunnel; --keep-vm leaves a cloud VM running for debugging.
Next
- Isolation and placement — the rules that decide which worker gets which sandbox.
- More than one instance — when the control plane itself is a fleet.