node family adds a machine, converges it again, renames it, assigns and moves its roles, records which Node may act on which, sets its apps root, and removes it.
Node provisioning explains what add, converge, role changes, and removal do on the machine. Node settings owns the apps root.
Commands
Every command accepts
--json. node:show, node:remove, and node:access:* take a numeric Node ID. The other commands also accept a Node name.
Lists use tables. Below the minimum table width, each row becomes a labeled record. Details use an aligned tree, with an em dash for an empty value. While the Gateway works, the CLI shows one progress indicator per request, because it does not receive the steps. JSON output contains only the response.
Destructive commands ask for confirmation. The default answer is No. Declining, Ctrl-C, or end of input exits 1 and changes nothing. Without a terminal, and with --json, pass --force. --offline and --purge-data never give consent.
orbit node:add
Add a machine to the fleet. For an existing Node, the same command converges it again after you change its TLD, roles, or settings.--role accepts app-dev, app-prod, database, metrics, websocket, gateway, and vpn. Add ingress and analytics with node:role:add. A Cluster Router comes from cluster:router:set.
node:add with --platform=macos and set --user and --orbit-user to the same existing account. The machine must already join WireGuard and accept the Gateway’s SSH key. No role is required. A role request fails with node.platform_unsupported before SSH. This path also enrolls an existing roleless peer whose SSH identity is not pinned, and it can correct that peer’s platform and architecture from the machine. See macOS Nodes.
orbit node:list
List every Node with its status, roles, and addresses.orbit node:show
Show one Node, with its architecture, WireGuard and LAN addresses, roles, and apps path. JSON includes the storedsettings and excluded_projects.
orbit node:metrics
Show one metrics snapshot of an active Node: CPU per core, memory, swap, load, uptime, pressure, and disks.
Human output shows bytes and durations in readable units. JSON keeps the raw numbers.
orbit node:converge
Re-apply the Gateway-rendered Orbit footprint of a Node: its agent files, Caddyfile, private-DNS listener on thevpn Node, ProxyCli collector, and annotator server files. The fleet rollout runs the same step on each Node. It never changes an Instance or a role, so it works on a Node that owns Instances, unlike node:add.
The table lists each artifact as
applied or unchanged, then the Node’s footprint digest. When every digest matches the one the Node last received, the Gateway sends the Node no command. A Node that is not an active, managed Linux Node fails with node.converge_unsupported. Converge the Orbit footprint lists the artifacts and error codes.
orbit node:rename
Change the name of a Node. The name is only a registry identifier. Keys, addresses, SSH identity, and roles stay the same.node:add cannot take the new name.
node:add of any Node. Relocate the gateway role uses a rename to free the name gateway for a new machine.
orbit node:settings
Set the apps root of a Node, the directory under which the Gateway creates development Instance checkouts.--setting. It sends {"apps":{"path":"/mnt/apps"}} to the settings API, without a settings wrapper. An empty value unsets the path: --setting=apps.path: sends {"apps":{"path":null}}. The API reference and MCP publish the same nullable path inside a closed apps object. A change never moves an existing checkout. Node settings lists the validation rules and failure codes.
orbit node:wireguard-key
Set or replace the WireGuard public key of an existing unmanaged Node. Use this when the operator manages the peer’s tunnel outside Orbit. The command does not generate a key pair, accept a private key, or configure the target machine.Node, then for an omitted key with the text prompt WireGuard public key, with no default. An invalid prompted key is requested again. With --json or without interactive input, missing arguments fail before mutation. Cancelling a prompt exits 1 and changes nothing. A supplied invalid key also fails before mutation.
The Gateway refuses a managed Node: a Linux or macOS Node with both a nonempty WireGuard address and a pinned SSH host fingerprint, regardless of its lifecycle status. It also refuses any Node holding the gateway role, regardless of that role’s status. These guards cannot be overridden. The target needs an existing WireGuard address; the command does not allocate one or change the Node’s status, roles, endpoint, address, or SSH identity.
WireGuard identifies peers by public key, not by Node ID. Before changing anything, the Gateway rejects a key assigned to any other Node, regardless of that Node’s status or the caller’s access to it, or equal to the hub’s public key, with node.wireguard_key_conflict (HTTP 409). The collision check and assignment are atomic across concurrent key assignments; the target’s name lock alone is not sufficient. The refusal leaves the stored key and hub unchanged and does not expose the other Node’s identity. Supply the public key of a distinct key pair configured on the target, rather than removing or changing another peer. The target’s own current key is allowed unless it also collides with another Node or the hub, so valid identical-key retries remain supported.
The Gateway stores the public key and reconverges the WireGuard hub on the vpn Node, using the existing hub projection. It does not converge the target Node’s footprint, contact it over SSH, or alter its host tunnel. Sending the same key still reconverges the hub, so a retry repairs a failed projection. Replacing the key can interrupt the target’s connectivity until its locally configured key pair matches; no --force option or confirmation is required because this is an explicit setting operation, not Node removal.
The request is synchronous. Human progress uses Set Node WireGuard key, Setting Node WireGuard key, and Set Node WireGuard key for pending, active, and success, without invented remote stages. Success prints WireGuard public key set for Node #<id> [<name>]. and Request ID: <uuid>. JSON emits only the standard Node response, including wireguard_public_key; errors use the standard error envelope. Success exits 0; validation, refusal, and projection failures exit 1. A lost connection or an interruption after sending the request does not prove the Gateway rolled back or completed publication. Activity by request ID helps correlate the attempt, but does not establish which key the hub is using.
Every authorized attempt records Activity as node:wireguard-key, with the caller, target Node, outcome, and request ID, but never the request body or private key material. A hub failure is not success: the newly stored key remains, the error identifies the failed projection step, and the operator retries the same command after fixing the hub. If the Gateway crashes after saving the key but before hub publication, the registry can contain the new key while the hub still uses the old configuration. If it crashes after publication but before recording completion, the hub can already use the new key while Activity later reports activity.interrupted. Activity alone cannot distinguish these windows. Do not assume rollback or automatic reconciliation. Restore an authorized management path to the Gateway, using another active peer with access to the target if the target’s tunnel is unavailable, then replay the same desired public key. The replay checks collisions again and republishes the hub even when the stored key already matches. Configure the unmanaged target’s local key pair yourself; replay never repairs that tunnel for you.
A successful projection preserves every non-target peer’s public key, allowed addresses, and other peer settings. The API entry defines the request, response, errors, and required collision, rendered-configuration, and injected-failure acceptance cases.
WireGuard key errors
The API also uses the shared identity, access, and not-found errors described in Using the API.
orbit node:role:add
Add one role to a Node, or converge an existing role again.vpn and router fail with validation.failed and Role [ROLE] is protected from generic mutation. A role that conflicts with one the Node holds fails with validation.failed before anything changes. Role compatibility lists the pairs. For analytics, a terminal lists the Docker Processes that run the right image and asks for each missing option. With --json or without a terminal, the command refuses. The Analytics role page describes that role.
Role operations on one Node run one at a time. A command that waits more than 2 minutes fails with node_role.convergence_failed, details.step converge:node-lock, and details.error_code node_role.node_busy. Run it again.
Some steps report a failure without failing the role. The role stays active, and follow_up in the response names the failure and the retry. The CLI prints it as a warning. The gateway role’s private DNS route reports this way.
Adding gateway grants the new Gateway Node access to the active vpn and metrics Nodes, when they are other Nodes. Prefer node:role:relocate to move gateway, websocket, or metrics.
orbit node:role:list
List the roles of one Node with their status and failed step.orbit node:role:relocate
Movegateway, websocket, or metrics from its current Node to another Node. One assignment stays active for the whole request.
gateway, the Gateway opens the orbit:gateway-https firewall rule on the target and moves the assignment. It grants the new Gateway Node access to the vpn and metrics Nodes, publishes private DNS on the vpn Node, and adds the private DNS route on the target. It then removes that route and the firewall rule from the source. A failed route on the target reports follow_up and does not stop the move.
Relocating gateway does not move vpn, the Gateway checkout, its SQLite database, the Orbit CA, or the SSH keys. It does not stop Caddy or PHP-FPM on the source, so clients that still use the old address keep working. Relocate the gateway role gives the full order.
For websocket and metrics, the Gateway copies role credentials that the target lacks, without overwriting any. It moves the assignment, converges the target, and removes the runtime and publication from the source.
When a step on the target or the source fails, the role already runs on the target. The command fails with its usual code, details.step set to converge:STEP or remove:STEP, and details.error_code set to the step’s own code. The message names the --from command that finishes the move. The Gateway reconciles Metrics once, after the source is done. A failed reconcile does not fail the move: follow_up names the Metrics Node and the orbit node:role:add <metrics-node> metrics --converge command.
orbit node:role:remove
Remove one role from a Node.app-dev cannot be removed while the Node owns Instances, and no role can be removed while a Route depends on it. Removing the last role restores public SSH first, as Public SSH describes.
The command removes a role that is active or failed. Each removal step is safe to repeat over what a failed convergence left. A role that is provisioning or removing is refused until its claim is stale. A failed removal leaves the role failed at remove:STEP and returns the step’s own code in details.error_code. Run the same command again.
When two removals of one role run together, the second waits for the role lock. If the first finished meanwhile, the second fails with node_role.already_removed (HTTP 409). A role the Node never had returns http.404, except ingress: a confirmed removal of an ingress role that the Node does not hold succeeds and changes nothing.
orbit node:access:add
Grant one Node access to act on another Node through the Gateway. An access grant authorizes Gateway commands. It does not change network reachability, which WireGuard membership already gives.
Both Nodes must be active. Adding a grant that exists reports that it already exists. A task VM Node never consumes a grant: the command fails with
task_vm.access_refused.
A grant to the active gateway Node lets the consumer act on every Node. The gateway Node itself, and the machine that the Gateway bootstrap recorded as its serving host, act on every Node without a grant.
orbit node:access:remove
Remove one access grant.orbit node:remove
Remove a Node record and its Gateway configuration. The Gateway restores public SSH on the machine, so you can add it again.gateway Node, or the vpn Node.
Removing a macOS Node also reports what stays on the machine. JSON includes retained_on_node, and the human output lists the same values: user, package-managers, and host-wireguard. Those are the account, the package managers, and the host WireGuard configuration. Declining the confirmation, or omitting --force without a terminal, cancels before the Gateway removes anything.
Remove a Node lists each step and failure code.
orbit node:excluded-project:add
Keep a Project off thisapp-dev Node for development placement. The Node must have an active app-dev role. project:excluded-node:add writes the same exclusion from the Project side. Development node exclusions owns the rules.
In a terminal, an omitted Project or Node opens a searchable selector. JSON and noninteractive calls must name both.
orbit node:excluded-project:list
List the Projects that cannot use the Node for development.orbit node:excluded-project:remove
Let the Project use the Node again. It asks for no confirmation, because it moves and stops nothing.Why it works this way
These reasons explain the design. Check them before you propose a change.A dedicated relocate for gateway
The Gateway process serves the API. Removing the lastgateway assignment and adding it again would drop the Gateway’s authority and the gateway.orbit DNS record in between, so the second request might not run. Relocate keeps one active assignment for the whole move. Two active gateway assignments are rejected, because the fleet needs one authority and one address. The machine that runs the Gateway process keeps its authority during a move, because the bootstrap records it as the serving host.
One relocate action for every relocatable role
Each role definition states whether the role is relocatable. Orbit does not infer it from singleton and mutable, because generic add and remove is a wider contract than a move.gateway, websocket, and metrics are relocatable. One relocate action runs the shared steps for all three: the singleton lock, the source check, the settings copy, and the assignment transfer. Role-specific work runs as a step for that role inside the same action. A new relocatable role adds the flag and its step, not a new command.
The target’s credentials win
A target that already holds working role credentials keeps them. Relocate copies only the credentials the target lacks, because overwriting a working identity would break its clients.Relocate moves no secrets
The checkout, the SQLite database, the Orbit CA, and the SSH keys live on the machine that runs the Gateway process. Copying them inside relocate would turn it into a secret-moving installer. You copy that state yourself and then converge the Gateway web stack on the new machine.vpn does not move
The WireGuard hub and private DNS stay on their original Node.vpn is protected from node:role:add, node:role:remove, and relocate.
Rename is its own verb
A rename changes one identity field and does not touch WireGuard. Removing and adding the Node again would delete its WireGuard peer and records.node:update is rejected as a name, because update means a partial field patch.