Add spokes to your fleet
A spoke is a machine that runs your coding agents. Your hub polls it over SSH across your Tailscale network and drives its tmux. Adding a spoke is four moves: install the tooling, join the tailnet, register the roster line, confirm reachability.
Prerequisites
- Your hub is on your tailnet (see Tailscale setup).
- The candidate spoke has the floor installed:
bash3.2+,tmux,git.
Step 1 — Install the spoke tooling
On the new machine, from inside the codebase you want to add:
curl -fsSL https://dev-mux.com/install | bash
That one line is the whole install. It asks which role (answer spoke), which codebase, and which hub to register with, then installs the tooling, wires the agent hooks, and registers the spoke for you.
Run the same line again from inside a second codebase on the same machine to add that one too — it detects the existing install and adopts just the new codebase, leaving your setup alone.
If you would rather do the wiring by hand:
REPO_ROOT="$PWD" dev install-hooks
# edit .dev/session.conf, then:
dev up
The REPO_ROOT="$PWD" prefix matters: without it, dev install-hooks targets the machine's *default* repo, which for a second codebase is the wrong one.
See Install for platform specifics (macOS PATH, Raspberry Pi, cloud VM), and your hub's own Help → Guides panel for the step-by-step version of this page — including an agent runbook you can paste straight into Claude Code.
Step 2 — Join the spoke to your tailnet
The spoke must be on the same tailnet as your hub so the hub can reach it by name:
tailscale up --ssh
Now the hub can address the spoke by its Tailscale name. No inbound port is opened; the spoke is only reachable to your tailnet, and only your hub polls it.
This step cannot be skipped or automated away. Your hub identifies a spoke by asking Tailscale who is on the other end of the connection — there is no password and no API token, so tailnet membership *is* the credential. A machine that is not on your tailnet cannot register itself and cannot be reached by the hub.
Step 3 — Register the roster line
Your hub keeps a roster of spokes. A roster stanza names the spoke, its host, the transport, and the repo path:
vm web1
host web1
transport ssh
repo /opt/web1
wake web1
You rarely write this by hand. The installer in step 1 registers the spoke for you, and there are two direct commands if you need them:
# from the spoke — self-register, authenticated by your tailnet identity
dev register --hub http://your-hub:8787 --repo /opt/web1 --name web1
# or from the hub — works over SSH, no dependency on the spoke's own tooling
devf adopt web1 /opt/web1 --name web1
On a managed hub this is done for you through the cockpit when you add a spoke — you supply the name, host, and repo path, and the hub appends the stanza atomically. Registration is idempotent: adding a spoke that is already in the roster is a no-op, never a duplicate.
Step 4 — Confirm the hub can reach it
From your hub, a single fleet health poll tells you the state of every spoke:
devf health-fleet
For a single new spoke, the two targeted checks are quicker. On the spoke:
dev onboard-check --hub http://your-hub:8787
Every line should start OK; a FAIL line names its own remedy. And from the hub, to prove the hub can drive it over SSH:
devf spoke-preflight web1
Each spoke reports one of: reachable (polling cleanly), quiet (reachable, nothing running), asleep (a Spot VM you can wake), or unreachable (genuinely down). A new spoke should read reachable within a poll cycle. If it does not, see Troubleshooting.
Spot VMs: sleep and wake
A preemptible / Spot cloud VM can be asleep rather than dead — stopped to save money, ready to wake on demand. The roster's wake line names the box to your wake tool. From the cockpit or the CLI you can power an asleep spoke back on, and the dashboard distinguishes asleep (wakeable) from unreachable (down). If you run no Spot VMs, you need no wake tooling at all.
Special-case spokes
- Raspberry Pi / user-owned nodes running Tailscale SSH. These enforce your tailnet's SSH policy on port 22, so a tagged hub node may be blocked by default. The clean fix is a tailnet ACL rule granting your hub SSH to the spoke, or a second SSH daemon on a non-22 port. See Troubleshooting for the
tailnet policy does not permit...message. - macOS spokes. Surface
devandtmuxon a systemPATH(Homebrew is not on the non-interactive path) and keep the repo on the internal disk. See Install. - Enterprise / 2FA-gated machines the hub cannot SSH into. Run these as local-only spokes (
--no-scaffold, user space). They keep localtmuxresilience and can push outbound signals to your hub, but the hub never initiates a connection to them — do not add them to the roster as an inbound target.
What durability means here
Once a spoke is in your fleet, the agent sessions you start run inside tmux on the hub side of the control plane and on the spoke itself. Your clients — laptop, phone, cockpit — use tmux-backed sessions with automatic reconnect, so disconnecting a client never interrupts the agents working on your spokes.
Next steps
- Updates — keep spoke tooling and your hub current.
- Troubleshooting — a new spoke that will not go reachable.