Skip to main content
A Node is a machine that the Gateway manages over SSH and reaches over WireGuard. The 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.
For macOS, use 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 stored settings and excluded_projects.

orbit node:metrics

Show one metrics snapshot of an active Node: CPU per core, memory, swap, load, uptime, pressure, and disks.
The Gateway reads the snapshot from the Metrics role through Grafana. It runs four queries for the Node’s exporter: one for memory, swap, load, and uptime, and one each for cores, pressure, and disks. Only the first decides whether the Node has metrics. The Gateway stores no sample. 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 the vpn 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.
The new name uses ASCII letters, digits, dashes, and underscores, at most 63 characters, and must be unique. Repeating the current name succeeds and writes nothing. The Gateway holds the lifecycle lock for the current name and then for the new name, so a concurrent node:add cannot take the new name.
After a rename, callers must use the new name or the numeric ID. The WireGuard hub configuration names each peer in a comment. That comment keeps the old name until the Gateway next projects the hub, for example on the next 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.
The command needs at least one --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.
A terminal prompts for an omitted Node with the shared Node data list labeled 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

Move gateway, websocket, or metrics from its current Node to another Node. One assignment stays active for the whole request.
For 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.
Removing a grant to the Gateway Node also revokes Grafana access that depends on it. The CLI warns when you removed your own access to the Gateway Node.

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.
The Gateway refuses while the Node owns Instances, Routes, Schedules, roles, Processes, or firewall rules. It never removes the caller’s Node, the gateway Node, or the vpn Node.
Use --offline when the Node does not answer. The Gateway probes first. A Node that answers follows online removal, skips firewall-recovery, and a Linux response has an empty retained_on_node. A Node that does not answer is not changed on the machine. retained_on_node then lists the Node agent, the Metrics exporter, and each shed role’s leftovers. macOS always lists user, package-managers, and host-wireguard.
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 this app-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 last gateway 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.