Skip to content

Running the agent in a container

The image carries one static binary, an unprivileged user, and an entrypoint that writes a configuration when none is mounted. Everything else is a parameter.

docker run -d --name senhub-agent \
  -e OTLP_BEARER_TOKEN=<your token> \
  -v senhub-state:/var/lib/senhub-agent \
  ghcr.io/senhub-io/senhub-agent:0.6.3

That is the whole of it for a first run: one variable, one mount.

Podman runs the same image with the same command; see Running the agent with Podman for what differs and for a Quadlet unit that runs it as a systemd service.

On Kubernetes, the Helm chart runs this image with its identity kept in a Secret, the configuration built from values, and optional read-only monitoring of the cluster.

Pin the exact version for a reproducible deployment. A stable release is also tagged with its minor line (:0.6), which follows the patch releases of that line; betas are never tagged that way, and there is no latest. See container image tags.

The one mount that matters

/var/lib/senhub-agent holds everything that makes this agent this agent: its host identity, its own key, and the bookmarks its log probes keep. Mount it, or every container arrives as a new host and its log probes lose their place: a Container Apps stream re-sends its last tail_lines, a file probe skips what was written in between. The entrypoint says so on startup when the directory is not a mounted volume, naming only what SENHUB_HOST_ID and SENHUB_AGENT_KEY do not already carry.

Two identities live there, and they answer different questions.

machine-id is the host identity. It is what a container has none of: on a real machine the operating system provides it, and the agent reads it to decide which host its measurements belong to. Without one the underlying library falls back to the kernel's boot identifier, which it documents as not stable, so each container is seen as a different machine. Measured on a real deployment: three runs of the same image, three hosts in the graph.

agent.key is the agent identity. It is generated on first start and it is what the receiving side reads to tell two agents apart, so two agents carrying the same key are one agent to everything downstream. Keeping it in the volume means one agent per volume, not one per container.

If your platform already knows what this host is, SENHUB_HOST_ID settles the first without a volume, and SENHUB_AGENT_KEY the second. With both set, a container without a volume comes back as the same host and the same agent. Give each instance its own values: an identity shared between several running agents is worse than one that changes, because nothing signals it, and an example or blank value is refused at start.

What survives a new container without a volume: the host identity and the agent identity when those two variables are set, the configuration the variables describe. What does not: the log bookmarks, so a Container Apps stream re-sends its recent lines and a file probe skips what was written in between, and anything written to the configuration from the console.

The host's name is another matter: it is the container's host name, which Docker sets to the container id unless told otherwise, so a new container shows up under a new name while keeping the same identity. Give it one that means something with --hostname (hostname: in a Compose file).

Nothing else needs a mount. The configuration lives inside the container unless you choose otherwise, and the log file is written to /var/log/senhub-agent inside it.

Azure Files: SMB or NFS

From 0.6.3 the agent is tested with the state directory (/var/lib/senhub-agent) and the configuration directory (/etc/senhub-agent) on an Azure Files SMB share mounted by Container Apps. An SMB share sets file modes and owners from its mount options and refuses chmod and chown; the agent skips a refused chmod/chown, seals secrets, keeps its identity (agent key, instance id), the logs queue, and the probe bookmarks in the state directory, and places its local status socket in a private directory under the temp directory because SMB has no Unix sockets.

Use SMB. It is the default Azure Files protocol on Container Apps and the tested one. NFS shares on Azure Files require a premium storage account and a virtual network; they are not tested with the agent.

Size the share from the logs queue: it is capped by SENHUB_LOG_QUEUE_MAX_BYTES (default 128 MiB) with a 24 h retention. Plan the share accordingly, a few hundred MiB per collector. A queued line takes about 290 bytes on disk: the default 128 MiB holds a 3-hour backend outage up to about 43 lines per second (measured: 3 hours at 20 lines per second, no line lost or duplicated, the backlog drained in about two minutes after recovery). For a busier collector, raise SENHUB_LOG_QUEUE_MAX_BYTES to rate × outage × 300 bytes; the records.volume metric of the Container Apps probe gives the rate.

Use one collector per share directory. Two agents must not share the same state directory.

Setting uid/gid in the share mount options so files belong to the agent's account is not tested yet.

Variables

None is strictly required. OTLP_BEARER_TOKEN is what a first run needs to export to SenHub; the rest have defaults or are only read when the feature they configure is wanted. These variables are read only when the entrypoint writes the configuration, that is when no agent.yaml is present (see Bringing your own configuration). The SENHUB_PROBE_<NAME>_* variables are the exception: the agent itself reads them, so they apply with a configuration of your own too (see Probes from variables).

Variable Required Default What it does
OTLP_BEARER_TOKEN No (needed to export to SenHub) - Authenticates the export to SenHub, sent as Authorization: Bearer and resolved from the environment at every start, never written to a file. Without it and without SENHUB_OTLP_ENDPOINT, no OTLP output is written: the agent collects and serves its local HTTP endpoints but exports nothing, and the entrypoint says so on startup. With SENHUB_OTLP_ENDPOINT set, the export goes out without an Authorization header
SENHUB_OTLP_ENDPOINT No eu-west-1.intake.senhub.io:443 when OTLP_BEARER_TOKEN is set Another collector: your own OpenTelemetry collector, VictoriaMetrics, Grafana Alloy
SENHUB_OTLP_PROTOCOL No grpc grpc or http, the latter for a backend that ingests OTLP over HTTP
SENHUB_ENTITIES No true Sends the entities (this host, the agent, what its probes watch) a topology backend builds its map from. false exports measurements and logs only. Any value other than true or false stops the container
SENHUB_OTLP_TLS No true false for a collector that listens in plain text, such as a sidecar on localhost:4317. The token then crosses the network unencrypted: keep it to the same host or a trusted network. Any value other than true or false stops the container
SENHUB_LICENSE No - Licence token, for the probes that need one
SENHUB_TAGS No - Tags on every metric, as key=value,key2=value2
SENHUB_ZABBIX_SERVER No - Zabbix server or proxy, host:port: writes the Zabbix output, and the container registers in Zabbix at its first contact
SENHUB_ZABBIX_HOST_METADATA No senhub-agent Host metadata the Zabbix autoregistration action matches
SENHUB_HTTP_PORT No 8080 Port of the console and of the PRTG, Nagios and Prometheus endpoints
SENHUB_HTTP_BIND No 0.0.0.0 Address the console and the PRTG, Nagios and Prometheus endpoints listen on. The container's own loopback is reachable by no one, so the image listens on every address and relies on the container network; 127.0.0.1 keeps them inside the container
SENHUB_CONFIG_DIR No /etc/senhub-agent Where the configuration is read and written
SENHUB_STATE_DIR No /var/lib/senhub-agent Where the identity, the key, the bookmarks and the logs queue (otlp-queue/) live
SENHUB_LOG_QUEUE No true false stops the agent keeping failed log batches on disk during an OTLP outage
SENHUB_LOG_QUEUE_RETENTION No 24h Age after which a queued log batch is dropped; 0 = no limit
SENHUB_LOG_QUEUE_MAX_BYTES No 134217728 (128 MiB) Disk cap of the logs queue, in bytes or with a suffix (64MiB). The queue lives in the state directory: put it on a persistent volume (a File Share on Container Apps) for logs to survive a restart; see Logs survive an outage
SENHUB_HOST_ID No kept in the state directory Gives the host a stable identity across the re-creation of its container, so a new container is the same host in the graph and not a new one. 32 hexadecimal characters, dashes optional. One value per host: an example or blank value (all zeros, 01234567-89ab-cdef-…) is refused at start, and the host entity is marked senhub.host.id.source=configuration
SENHUB_AGENT_KEY No kept in the state directory Agent identity, a UUID. One value per instance; with SENHUB_HOST_ID it lets a container without a volume keep one identity
SENHUB_PROBES No - YAML of the probes to run, as a probes.d file would hold it. For one probe at a time, the SENHUB_PROBE_<NAME>_* variables are shorter, see Probes from variables
SENHUB_OUTPUT No - YAML of one more output, as a strategies.d file would hold it

Upgrading: an agent.yaml that is already there wins

The variables above are read only when no agent.yaml exists in SENHUB_CONFIG_DIR. A configuration directory kept on a volume since an earlier run holds one, so a new container started with other values (another token, endpoint or tag) ignores every SENHUB_* variable of the table and keeps the old file; the entrypoint says so on startup. To apply them, remove agent.yaml from the volume before the new container starts, or edit it. SENHUB_PROBE_<NAME>_* is the exception: the agent reads it itself at every start.

Without SENHUB_AGENT_KEY the agent generates its own key on first start, and the entrypoint keeps it in the state directory so the next container reuses it. That is why the mount matters.

Never bake either identity into an image. An image is deployed in several copies by construction, so an identity that belongs to the image belongs to all of its instances at once.

Starting other probes

The variables above configure the agent. The probes are configuration of their own, and there are sixty-six types of them, so they are not each given a variable each. Three ways in, from the least to the most work, and a fourth that adds to any of them:

A variable carrying the fragment

SENHUB_PROBES holds the same YAML a file in probes.d would. It is the way in on a platform where placing a file is harder than setting a variable, which is the usual case for a managed container service:

SENHUB_PROBES: |
  - name: db
    type: mysql
    params:
      host: "${env:DB_HOST}"
      username: monitor
      password: "${env:DB_PASSWORD}"
  - name: web
    type: http_check
    params:
      targets: ["https://example.test/health"]

A ${env:...} reference inside it is resolved by the agent at every start, so a password is set as its own variable and never appears in this one. SENHUB_OUTPUT does the same for one output.

A mounted drop-in directory

Mount /etc/senhub-agent/probes.d and drop one file per probe. The entrypoint still writes agent.yaml and the output from the variables, so you keep the one-variable start and add only what the platform can mount.

A whole mounted configuration

Mount /etc/senhub-agent whole. Nothing is written and the variables of the table are ignored, which the entrypoint says on startup; SENHUB_PROBE_<NAME>_* still applies, the agent reading it itself. This door has a trap of its own when the same directory serves several instances: see Bringing your own configuration.

Whichever you choose, the configuration is checked before the agent starts: a container whose variables produce a file the agent would refuse stops with the reason rather than restarting in a loop.

Probes from variables

SENHUB_PROBE_<NAME>_TYPE declares a probe and SENHUB_PROBE_<NAME>_<PARAM> sets its parameters, one variable each, typed from the probe's schema. It is a rule of the agent, not of the image: it applies the same way under systemd, Helm or Podman, and it applies even when you mount your own configuration, where it adjusts the probes of your files instead of being ignored. A secret is read from a file with the _FILE suffix, as Docker and Kubernetes deliver them.

SENHUB_PROBE_DB_TYPE: mysql
SENHUB_PROBE_DB_HOST: db.internal
SENHUB_PROBE_DB_USERNAME: monitor
SENHUB_PROBE_DB_PASSWORD_FILE: /run/secrets/db_password

SENHUB_PROBE_<NAME>_* overlays the probes of the files and of SENHUB_PROBES, key by key, and the variable wins. The whole rule is on Configuration. Reading Azure Container Apps takes the same form, see Azure Container Apps.

Bringing your own configuration

Mount a directory on /etc/senhub-agent and the entrypoint writes nothing: your files win and the SENHUB_* variables of the table are ignored (SENHUB_PROBE_<NAME>_* is not: the agent reads it itself and it overlays your probes). The entrypoint says which of the two it did on startup, so a container that ignores your variables tells you why.

docker run -d --name senhub-agent \
  -v /srv/senhub/conf:/etc/senhub-agent \
  -v senhub-state:/var/lib/senhub-agent \
  ghcr.io/senhub-io/senhub-agent:0.6.3

Your configuration wins completely, and that includes the agent key under agent: key:. The entrypoint does not touch it, so a configuration shared between several instances makes them one agent: the receiving side reads that key to tell agents apart, and their health metrics land on the same series with different values. Nothing signals it, and the counters it hides are the ones read when looking for missing data.

With a mounted configuration, each instance must therefore carry its own key. Reference it from the environment, which works because substitution is applied to the whole file before it is parsed, and give every instance its own SENHUB_AGENT_KEY:

agent:
  key: "${env:SENHUB_AGENT_KEY}"

The agent resolves that reference itself, even though the entrypoint ignores SENHUB_* variables in this mode. Do not leave the key out or empty: the loader refuses a configuration whose agent key is empty, and the container stops at start.

What the container does not do

  • No service commands. install, start, stop and their kin manage a system service and have no meaning here. The agent is the container's main process.
  • No privileges. The image runs as uid 10001. Nothing in the agent needs root to run, but a few things need access the container may not have: reading the host's journal needs that journal mounted, the dependency discovery cannot attribute sockets to processes without the host's process namespace, and the hardware serial is not readable from inside a container.
  • No auto-update. Updating means pulling a new image tag. Under Podman, see Updates.

Network flows

Destination Port / protocol Purpose
SENHUB_OTLP_ENDPOINT SENHUB_OTLP_PROTOCOL (grpc or http), usually 4317 for gRPC or 4318 for HTTP, HTTPS/TLS as configured OTLP export
login.microsoftonline.com (or the authority_host parameter) 443/HTTPS Entra ID token for Azure Container Apps and jobs probes
management.azure.com (or the management_host parameter) 443/HTTPS Azure Resource Manager for Azure Container Apps and jobs probes
*.azurecontainerapps.dev 443/HTTPS Log stream endpoint of each Container Apps environment
eu-west-1.intake.senhub.io 443/HTTPS Release check when auto-update is enabled. Normal container updates change the image tag; the platform pulls the new image from ghcr.io

All hostnames above require DNS resolution.

Permissions for the Azure probes are documented in Azure Container Apps and Azure Container Apps jobs.

Nothing listens inbound except the optional console/HTTP port and the OTLP receiver when enabled.

Health

The image declares a health check against the console's /health. It answers as soon as the HTTP output is listening, which is what tells a scheduler the agent is up.