Enroll a PPN node
Prerequisites
- The
service-vm-hostbinary deployed on the machine you are enrolling - Network reachability from that machine to the fleet controller on port 9203 — the controller's port is hardcoded and not configurable
- A node identifier unique within the fleet
- The node's WireGuard IP address
- systemd, or another supervisor that restarts a failed process
Purpose
Register a machine with service-vm-fleet by starting the per-node heartbeat agent on it — about five minutes, after which the node appears in the controller's listing within one heartbeat interval.
Procedure
-
Set the three required environment variables for
service-vm-host. The agent has no CLI flags and reads no config file; it refuses to start unless all three are present:VM_FLEET_ENDPOINT=http://<fleet-controller-host>:9203 VM_NODE_ID=<unique-node-identifier> VM_WG_IP=<node-wireguard-ip> -
Optional: override any of the three defaults, each of which is usable as shipped:
Variable Default Effect VM_HEARTBEAT_INTERVAL_S10Seconds between heartbeats to the controller VM_SPAWN_PORT9204Port the agent's own local API listens on VM_RESERVEDfalseMarks the node last-resort-only for placement -
Start
service-vm-hostunder systemd, configured to restart automatically on failure. The agent begins heartbeating toPOST /v1/nodes/heartbeatimmediately and needs no separate registration call first. -
Confirm the node reached the controller:
Expected outcome
The node appears in GET /v1/nodes and GET /v1/fleet within one heartbeat interval — ten seconds with the default. Its record carries node_id, hostname, wg_ip, ram_available_mb, vm_count, kvm_available, reserved, and last_heartbeat as an RFC3339 timestamp.
Verification
Look for your VM_NODE_ID with a last_heartbeat no older than one heartbeat interval. There is no status field on a node record — recency of last_heartbeat is the only liveness signal the controller exposes, and a node that stops heartbeating for more than 30 seconds is dropped from the listing entirely rather than shown as stale or offline. A node absent from /v1/nodes is therefore either never-enrolled or more than 30 seconds silent; the API does not distinguish the two.
Note:
service-vm-fleethas no/healthzroute. To confirm the controller itself is answering, callGET /v1/fleet.
Note: each heartbeat replaces the controller's record of that node wholesale — VMs and resource stats are overwritten, not merged into the previous state.
Note: to inspect a VM, list VMs with
GET /v1/vms(optionally filtered with?tenant_id=) and filter client-side.DELETE /v1/vms/:vm_idexists, but there is no GET for a single VM by id.
Warning: fleet state is held in memory on the controller only. If the controller process restarts, every node and VM record is lost until nodes heartbeat again. No ledger entry is written on enrollment or on heartbeat, so a restart leaves no record of what the fleet contained beforehand.
Rollback
Stop the service-vm-host process. After roughly 30 seconds without a heartbeat the controller drops the node from its listing on its own. No unenroll call is needed, and none exists.
Re-enrolling is the same procedure: start the agent again with the same VM_NODE_ID and the node reappears at the next heartbeat.
Next steps
- Add a node to a running fleet — the fleet-side companion procedure
- service-vm-fleet — the PPN VM fleet controller — the controller's state model and route surface
- Private Platform Network: pooled compute from hardware you already own — the fleet architecture this node joins
- os-infrastructure — PPN node operating system — the node image built around this agent
Cite this record: /wiki/enroll-ppn-node — revision f99f9774, last updated 6 August 2026.