Install the agent on a VM
On a Linux VM the agent runs under systemd and describes the machine it runs on: the host, its systemd services, its Docker containers, its listening ports and, if you turn it on, its installed packages. This is host mode, EVOHUB_PLATFORM=vm, which the unit file sets for you.
Before you start, create the environment in Catalog and an enrollment token for it, choosing Linux VM under Where it runs; see Enrollment. One environment can hold many VMs, each with its own agent, and it is billed once.
Install
The agent is published as static binaries for linux/amd64 and linux/arm64, with its systemd unit, beside the image on ghcr.io, one tag per file. Fetching them needs only curl and sha256sum.
The console's New agent dialog shows the complete install for your version, with the token and the agent's name filled in, as One command or as Keep it out of shell history. It runs these steps:
Fetch the binary and the unit
Download the release's binary for the machine's architecture and the evohub-agent-host.service unit, each checked against the digest its manifest names.
Install the binary and create the agent's user
sudo install -m 0755 evohub-agent /usr/local/bin/evohub-agent
sudo useradd --system --home-dir /nonexistent --shell /usr/sbin/nologin evohub-agent
Store the enrollment token
sudo install -d -m 0700 -o evohub-agent /etc/evohub-agent
sudo install -m 0600 -o evohub-agent /dev/stdin /etc/evohub-agent/enrollment-token
# paste the token, press Enter, then Ctrl-D
What the machine runs appears in Catalog within a few minutes, under the provider host, and the agent shows as Reporting under Organization → Agents.
Verify a release
The fetch checks that each file is byte for byte the one its manifest names. That proves integrity, not origin. To know the file is EvoHub's, verify the manifest's Sigstore signature by the digest the fetch printed, not by the tag:
cosign verify ghcr.io/evolutionarysynchronization/evohub-agent@sha256:<manifest digest> \
--certificate-identity-regexp '^https://github.com/EvolutionarySynchronization/evohub-agent/\.github/workflows/release\.yml@refs/tags/v' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com
The binaries' SHA256SUMS is signed the same way (SHA256SUMS.sigstore.json, checked with cosign verify-blob --bundle).
Settings
Settings go in a drop-in for the unit (sudo systemctl edit evohub-agent-host), under [Service]:
[Service]
Environment=EVOHUB_AGENT_NAME=web-01
Environment=EVOHUB_HOST_PACKAGES=true
| Variable | Default | |
|---|---|---|
EVOHUB_AGENT_NAME |
the hostname | How the agent appears in EvoHub |
EVOHUB_API_URL |
https://api.evohub.io |
EvoHub's API, scheme and host only |
EVOHUB_HOST_PACKAGES |
false |
Report installed packages (dpkg-query or rpm) |
EVOHUB_DOCKER_SOCKET |
/var/run/docker.sock |
Where to list containers; empty turns containers off |
EVOHUB_SWEEP_INTERVAL |
5m |
How often to run a full sweep, 1m to 24h |
EVOHUB_HEARTBEAT_INTERVAL |
1m |
How often to send a heartbeat, 10s to 3m |
HTTPS_PROXY, NO_PROXY |
none | An egress proxy |
Restart the unit after changing them: sudo systemctl restart evohub-agent-host.
Docker containers
The agent lists Docker containers only if it can reach Docker's socket. Add SupplementaryGroups=docker to the drop-in to allow it.
Warning
Membership of the docker group is equivalent to root on that machine: whoever controls the agent could then start a privileged container, and the unit's sandbox no longer protects the machine from it. The agent only lists containers. If that matters to you, put a read-only proxy for Docker's API (one that allows GET /containers/json only) in front of the socket and point EVOHUB_DOCKER_SOCKET at it.
How it runs
The unit runs the agent as its own system user, evohub-agent, with a read-only view of the system: the only writable place is its private state directory, where it keeps its key (/var/lib/evohub-agent/identity.json, mode 0600). It needs outbound HTTPS and nothing else.
It sees which user owns each listening socket, not which process, so a port is put down to the one service running as that user, and left unattributed when that user is root or runs several services. Running the agent as root would name every port's process; that is your decision, not the unit's default.
What it reads and sends
| It reads | For |
|---|---|
/etc/machine-id |
The host's identity; every resource is named host://<machine id>/<Kind>/<name> |
/etc/os-release, /proc/sys/kernel, /proc/cpuinfo, /proc/meminfo, /proc/stat, /proc/mounts |
What the machine is |
/sys/class/dmi/id/sys_vendor, /var/lib/cloud/data/instance-id, /sys/class/dmi/id/board_asset_tag |
Which cloud, and its instance ID; no metadata endpoint is called |
systemctl list-units, list-unit-files, show |
Its services |
/proc/net/tcp, tcp6, udp, udp6, /etc/passwd (user names and IDs only) |
What listens, and as which user |
/proc/<pid>/fd, comm, cgroup |
Which process holds a socket, where the agent may read it (as root) |
| Docker's socket, only if you allow it | Its containers |
dpkg-query or rpm, only with EVOHUB_HOST_PACKAGES=true |
Installed packages |
It sends:
- Host: hostname, distribution, kernel, architecture, CPU count, memory, each local disk's mount point, filesystem and size (a network mount's server name, not its path), IP addresses, boot time, cloud and instance ID.
- Service, a systemd unit that is enabled, running, starting or failed: its unit file's path, whether it is enabled, its type, its description, the path of the binary it starts and the user it runs as. Never its arguments, its environment or its drop-ins.
- Container: its name, image and image digest, published ports, and the Compose project and service it belongs to. No command, environment, mounts or other labels.
- ListeningPort, one per protocol and port: the addresses it is bound to, and the service, container or process name that listens, when the agent can tell. Never a command line.
- Packages, if turned on: each installed package's name, version and architecture.
- Live state, every minute: whether the host, each service and each container runs as it should, and the name of what is wrong (
Failed,Restarting,Unhealthy,Error).
It never reads or sends file contents, logs, shell history, anything from a home directory, any process's environment variables or command-line arguments, or what a service or container is doing (connections, traffic, metrics). Every document goes through the same redaction as a Kubernetes object before it is hashed or sent.
In Catalog, each container and non-system service is an application of its own (a Compose project is one application), a port is linked to what listens on it, and services and containers are shown running on their host.
Upgrade
The agent never upgrades itself. To upgrade, fetch the new version's binary as in the install, replace /usr/local/bin/evohub-agent with it, and restart the unit:
sudo install -m 0755 evohub-agent /usr/local/bin/evohub-agent
sudo systemctl restart evohub-agent-host
Its key stays in its state directory, so it keeps its identity and history.
Uninstall
sudo systemctl disable --now evohub-agent-host
sudo rm /etc/systemd/system/evohub-agent-host.service
sudo rm -rf /etc/systemd/system/evohub-agent-host.service.d /etc/evohub-agent /var/lib/evohub-agent
sudo rm /usr/local/bin/evohub-agent
sudo systemctl daemon-reload
Removing /var/lib/evohub-agent deletes the agent's key. Then revoke the agent under Organization → Agents; until you do, it stays enrolled. What it reported stays in Catalog and is shown as stale.

