rpk discover reads your infrastructure and writes it out as RackPeek YAML, so you
don't have to type in what the machine already knows about itself.
| Command | Reads | Produces |
|---|---|---|
rpk discover system |
the machine it runs on | one System resource |
rpk discover docker |
the Docker Engine API | one Service per published container, plus the System they run on |
rpk discover proxmox |
a Proxmox VE cluster | a Server and System per node, a System per guest, already wired together |
rpk discover network |
a subnet, from outside | one System per host that answers ping or a well-known TCP port |
Both print YAML to standard output by default and change nothing, so it is always safe to run one and look at the result first.
# Look at what this machine reports
rpk discover system
# Save it
rpk discover system > nas01.yaml
# Send it straight to your RackPeek server
export RPK_SERVER=http://rack.lan:8080
export RPK_API_KEY=your-shared-secret
rpk discover system --push
--push uses the same Inventory API as any other import, so the
server needs RPK_API_KEY set. Discovery always merges — it can add and update, and
never removes anything it did not find.
The problem with re-running discovery is names. A RackPeek resource is identified by its name, and names are yours to choose — but a machine only knows its hostname. Run discovery twice, rename something in between, and a naive tool gives you two resources.
Each discovered resource therefore carries a discoveryId:
- kind: System
name: nas01
discoveryId: rpk1:sys:a3f9c2e1b8d47e60
type: baremetal
os: Debian GNU/Linux 12 (bookworm)
The id is derived from something stable about the machine — /etc/machine-id on Linux,
the platform UUID on macOS — hashed, so no raw machine identifier ends up in a config
file you might commit. The same machine produces the same id every time, with nothing
stored locally, from any machine you run the command on.
What that buys you:
storage-01 in the web UI and the next discovery run
updates storage-01. It will never rename a resource you named.nas01 by hand, the
first discovery run attaches to it — keeping your notes and gaining an id — rather
than creating a duplicate.Cloned VM templates share
/etc/machine-id. If you clone a Proxmox or VMware template without resetting it, every clone reports the same identity. RackPeek rejects a payload containing duplicate ids rather than silently merging the machines. Runsystemd-machine-id-setupon the clones, or reset it in the template before cloning.
rpk discover systemSupported on Linux and macOS. Reports hostname, OS, cores, RAM, primary address, and whether the machine is bare metal, a VM or a container. Disks are included on Linux.
rpk discover system --name nas01
| Option | Meaning |
|---|---|
-n, --name <NAME> |
Name for this machine. Defaults to its hostname. |
--push |
Upload instead of printing. |
--server <URL> |
Server to upload to. Defaults to RPK_SERVER. |
--api-key <KEY> |
API key. Defaults to RPK_API_KEY. |
--dry-run |
Ask the server what would change, without changing it. |
Anything the host cannot answer is left out rather than guessed at, and the merge treats a missing field as "leave whatever is already there alone".
Because re-runs update rather than duplicate, this is safe to put on a timer. On a systemd host:
# /etc/systemd/system/rackpeek-discover.service
[Service]
Type=oneshot
Environment=RPK_SERVER=http://rack.lan:8080
Environment=RPK_API_KEY=your-shared-secret
ExecStart=/usr/local/bin/rpk discover system --name nas01 --push
# /etc/systemd/system/rackpeek-discover.timer
[Timer]
OnCalendar=daily
Persistent=true
[Install]
WantedBy=timers.target
Passing --name is worth it here: it pins the resource name so a hostname change does
not look like a new machine.
rpk discover dockerReads the Docker Engine API and emits every container with a published port as a
Service, pointed at the host it runs on. For a local engine the host's own System
resource rides along in front of the services — that is what lets the server keep
runsOn pointing at the right resource even after you rename the host (the id travels
with the System; the services only know a name).
rpk discover docker
| Option | Meaning |
|---|---|
--docker-host <URI> |
Docker endpoint. Defaults to DOCKER_HOST, then /var/run/docker.sock. |
--host <NAME> |
Name of the machine the containers run on. Defaults to its hostname. |
Plus the same --push / --server / --api-key / --dry-run options as above.
Containers nothing outside the host can reach are skipped and counted in a note — no
published port, or every binding on a loopback address (-p 127.0.0.1:5050:80), which
only the host itself can reach. A binding pinned to one interface
(-p 192.168.1.21:8443:8443) is recorded at that address rather than the host's. Stopped
containers are never listed for the same reason — the daemon does not create host port
bindings until a container runs, so there is no address to record.
A container's compose project becomes a tag, so a stack stays grouped. runsOn points at
the same name rpk discover system produces on that machine, so running both gives you a
connected tree.
# A remote daemon behind a read-only socket proxy
rpk discover docker --docker-host tcp://192.168.1.20:2375 --host nas01
# Podman speaks the same API
rpk discover docker --docker-host unix:///run/user/1000/podman/podman.sock
For remote hosts, exposing the socket through a read-only proxy such as
tecnativa/docker-socket-proxy with
only CONTAINERS=1 is the safer arrangement — the same one the
docker-gen guide describes.
Over TCP the machine running the command is not the machine running the containers, so
nothing probed locally is attributed to the engine. Instead the engine is asked about
itself (GET /info): its daemon id seeds the services' identities — the same ids no
matter which machine runs the command — and its hostname is what runsOn points at,
which is the same name rpk discover system reports on that box. Services are recorded
at the endpoint's address (resolved once if you dialled a name). No System resource is
emitted for the host itself; document it with rpk discover system on that machine, or
by hand, and the services attach to it by name.
If you rename that host in RackPeek, re-discovery keeps your link: an update whose
runsOn points at nothing that exists leaves the stored link alone. A runsOn that
does name a real resource is recorded — that is a genuine move.
A proxy restricted to CONTAINERS=1 blocks /info, and discovery says so and degrades:
the endpoint itself becomes the identity seed (so keep addressing the engine the same
way — switching between an IP and a hostname would re-mint every id), and --host is
how to name the machine the containers run on. Allowing INFO=1 on the proxy removes
both caveats.
rpk discover proxmoxReads a Proxmox cluster and emits its nodes and guests as Systems, with runsOn
already pointing each guest at the node it runs on. That tree is the tedious part to
type by hand, and it is the reason this collector is worth more than its fields suggest:
one call inventories the whole estate without installing anything on the guests.
rpk discover proxmox --host https://pve.lan:8006 --insecure
| Option | Meaning |
|---|---|
--host <URL> |
Proxmox host. A bare name gets https:// and :8006. |
--token-id <ID> |
API token id, e.g. root@pam!rackpeek. Defaults to RPK_PVE_TOKEN_ID. |
--token-secret <SECRET> |
Token secret. Defaults to RPK_PVE_TOKEN_SECRET. |
--insecure |
Accept a self-signed certificate. |
Plus the same --push / --server / --api-key / --dry-run options as above.
In the Proxmox UI: Datacenter → Permissions → API Tokens → Add. Give it a read-only
role (PVEAuditor is enough) and clear "Privilege Separation" only if you need to.
export RPK_PVE_TOKEN_ID='root@pam!rackpeek'
export RPK_PVE_TOKEN_SECRET='xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
rpk discover proxmox --host pve.lan --insecure --push
--insecure is needed more often than not: Proxmox ships with a self-signed certificate
and most installations keep it.
A node becomes two resources, because it is two things:
kepler) — the machine, carrying its processor
(model, cores and threads per socket), memory, physical disks with Proxmox's own
nvme/ssd/hdd classification, and its GPUs;hypervisor (kepler-pve) — the Proxmox install running on that
machine, carrying the PVE version.Guests then run on the hypervisor, giving the full Hardware → System → System tree that the graph views are built around.
- kind: Server
name: kepler
cpus:
- model: AMD Ryzen 5 5600G
cores: 6
threads: 12
ram:
size: 63
drives:
- type: nvme
size: 932
gpus:
- model: GeForce RTX 3090
- model: GeForce RTX 3090
- kind: System
name: kepler-pve
type: hypervisor
os: Proxmox VE 8.2.2
runsOn: [kepler]
- kind: System
name: docker-01
type: vm
runsOn: [kepler-pve]
Each QEMU guest becomes a vm and each LXC guest a container, with its allocated
cores, memory and its Proxmox tags. A container with a static address keeps it; one on
DHCP reports none rather than a wrong one.
Every disk is recorded, not just the boot one. The guest list only reports the boot disk, so a VM with a 64 GB root and a 2 TB data volume would otherwise appear as a 64 GB machine; the guest's config is read for the full set. Container mount points count too. Install media, detached volumes and the few megabytes of EFI or TPM scratch space are left out. The storage backend says nothing about the underlying medium, so guest disks carry a size but no nvme/ssd/hdd type — unlike the node's own disks, which Proxmox has already classified.
Stopped guests are included — unlike a stopped container, a stopped VM is still a real system with real resources.
A GPU passed through to a guest is recorded on the Server, not the guest — the card is bolted into the host, and a System has nowhere to put one. Integrated graphics are included too, since they are equally present. VRAM is not something the PCI device list knows, so it is left off.
The guest that holds a card gets a gpu label naming it, so the assignment is
visible from either end:
- kind: System
name: ai
type: vm
labels:
gpu: GeForce RTX 3090, GeForce RTX 3090
runsOn: [kepler-pve]
A label rather than a field, because RackPeek has no first-class way to say "this device is assigned to that system". PCI addresses repeat on every machine, so a guest is only ever matched against cards in the node it actually runs on.
The hardware detail needs the same permission as the node status call. Without it you still get the Server, the hypervisor and the whole guest tree — just without the processor and disks.
Running rpk discover system --with-hardware on the node itself gives better hardware
data still, since it reads real DMI rather than Proxmox's second-hand view.
A guest is identified by its vmid within the cluster, so renaming it in Proxmox, or migrating it between nodes, still updates the same RackPeek resource. A standalone host with no cluster uses its node name as the scope instead.
Known limitation. Proxmox identifies a guest by vmid; the guest identifies itself by its machine-id. Neither can derive the other, so running both
rpk discover proxmoxandrpk discover systeminside the same guest produces two resources rather than one. The second is reported as an addition with a suffixed name, so it is visible rather than silent — but pick one collector per guest for now.
rpk discover networkThe collector for machines nothing else can describe: no agent, no API — just an address that answers. It sweeps a subnet and emits one System per responding host, with its IP, its reverse-DNS name, and its MAC address as a label.
# Sweep this machine's own subnet and look at the result
rpk discover network
# Sweep a specific block, then merge it into the server
rpk discover network --cidr 192.168.1.0/24 --push
A host counts as alive when it replies to ping or accepts a TCP connection on any
probed port — plenty of gear drops ICMP, so ping alone would miss half a homelab. The
default port list is a curated homelab set (ssh, http/https, dns, smb, rdp, ipp,
proxmox, and friends); --ports 22,80,443 narrows or widens it. The ports are only a
liveness check: the sweep records that the host exists, not what it serves — pair it
with rpk discover docker or hand-written Service cards for that.
Sweeps are capped at a /16 (65,534 addresses). --timeout and --parallel tune how
patient and how aggressive the sweep is; the defaults finish a quiet /24 in seconds.
A network of several VLANs is several sweeps — each merges into the same inventory, and the ids keep re-runs honest:
rpk discover network --cidr 10.0.20.0/24 --push # the LAN
rpk discover network --cidr 10.0.50.0/24 --push # the server VLAN
A scanned host is identified by its MAC address, read from the ARP table the
sweep itself populates — so a DHCP re-lease updates the same resource's address rather
than inventing a new machine. One MAC answering on several addresses (a gateway's
VIPs and aliases) is still one machine and becomes one card: the lowest address as
its ip, every address in an ips label — so a VIP failing over never moves the
machine's identity. Two caveats:
rpk discover system records the machine's physical MAC addresses (a macs label),
and a scan identifies machines by exactly those MACs — so the two collectors land on
the same card, whichever ran first:
The card keeps whatever name it already had (names are always user-owned), so a
scan-first card keeps its generated host-… name until you rename it once. A MAC that
two stored cards both claim unifies nothing — ambiguity always falls back to separate
cards — and agent-grade identities never unify with each other on a MAC alone (cloned
VMs can share one; that is what machine-ids are for). The machine running the sweep
finds itself, and unifies with its own rpk discover system card the same way.
rpk discover proxmox joins the bridge for guests: a guest's config names the
NIC MACs Proxmox assigned it, so a VM or container found by a sweep and the same guest
reported by the Proxmox collector become one card too. Nodes stay outside the bridge
(the API exposes no host MACs we read), and a guest documented both by Proxmox and by
rpk discover system inside it remains two cards — vmid and machine-id are both
agent-grade identities, and MACs alone never unify those.
The sweep is a burst of pings and TCP connection attempts — the polite end of network scanning, but scan networks you operate, not networks you merely use.
--dry-run asks the server what would change and writes nothing:
rpk discover system --dry-run
updated nas01
Dry run — nothing was written to http://rack.lan:8080.
Without a server, redirect the output and read it:
rpk discover docker > services.yaml
Then import it through the web UI's Import YAML tool, which shows you the same diff.