.deb / .rpm, containerized. Instead of a cron entry on the host, the image’s entrypoint (scan-host-users.sh) scans the host user homes that are bind-mounted into the container, reading /etc/passwd to enumerate every account and running one privilege-dropped scan per user home.
Use this when you’d rather ship a container than a native package — for example on Kubernetes nodes, or hosts already managed as container workloads. For native-package and distro context, see Linux (Detect only).
What it is
- The same Detect-only all-users scanner as the Linux package: it enumerates all passwd users with an existing (bind-mounted) home — root and service accounts included, shared homes deduped — and reports MCP servers, connectors/plugins, skills, and agents to your Runlayer tenant.
- Not a sandboxed app. Like the
.deb/cron model, it needs to read every user’s home directory, so it requires a privileged read-only mount of the host filesystem. It is not a self-contained workload that only sees its own filesystem. - Detect only. There are no hooks, endpoint modes, Sessions, or browser extension. In the dashboard the device shows Detect = Active and Protect / Enforce / Sessions = Disabled — the expected state, not an error. The image is Detect-only out of the box, with or without a mounted config.
- Local passwd only. The container enumerates users from the mounted host
/etc/passwd. Centrally-managed accounts that exist only in a directory service (SSSD/LDAP) are not enumerated — even if their homes are mounted — because the container cannot query the host’s directory services. The native.deb/.rpmscanner can, so it does not have this gap. - Directory-service fleets: mount snapshots. Generate them on the host via cron —
getent passwd > /etc/runlayer/aiwatch/passwd-snapshotandgetent group > /etc/runlayer/aiwatch/group-snapshot— and mount them asRUNLAYER_HOST_PASSWD/RUNLAYER_HOST_GROUP. Include the group snapshot: without it, each user is scanned with their primary group only, which can miss group-readable homes and shared project directories.
Image
The scanner image is published to Runlayer’s private container registry and referenced as:- amd64 only. The image is
linux/amd64. On an arm64 host it runs under emulation (Rosetta / qemu). - The image is self-contained, so there is no host glibc requirement — unlike the
.deb/.rpm, which have a host glibc floor (see Linux (Detect only) for the current support matrix).
Standalone docker run
Mount only the paths the scanner reads, each read-only under /host — not the whole host root. RUNLAYER_HOST_HOME_PREFIX=/host makes the host-absolute passwd homes (/home/alice) resolve to the mounted copies inside the container. This covers users under /home and /root; homes elsewhere (service accounts under /var/lib, custom /srv homes) are not silently missed — the scanner logs each skipped user and a scanned/skipped summary each pass, so you add the prefix if you need it (see below).
RUNLAYER_SCAN_INTERVAL seconds (default 900 = 15 min), repeat. A failing pass is logged and the next pass retries — the container does not exit.
For a one-shot pass (single scan then exit — e.g. driven by a host cron entry or a systemd timer that runs docker run on a schedule), pass --once or set RUNLAYER_RUN_ONCE=1:
RUNLAYER_API_KEY the entrypoint exits 0 quietly before any scan (the unconfigured-fleet gate), so an unconfigured container produces no errors and no data.
A note on :ro: read-only bind mounts are not recursive — filesystems mounted inside /home (NFS/EBS/tmpfs homes) can remain writable through the mount. The scanner only writes per-user ~/.runlayer state (logs), the same as the native .deb scanner, so the exposure is benign — but don’t read :ro as a hard guarantee for submounted homes. On Kubernetes 1.30+ you can opt into recursiveReadOnly (commented in the manifests) to close this.
Need to cover homes outside /home and /root? Add the prefix as another read-only mount (e.g. -v /var/lib:/host/var/lib:ro) — keep the scope as narrow as your passwd layout allows. As a last resort you can mount the whole root (-v /:/host:ro) for maximal coverage, but that exposes far more of the host to the container than the scanner needs; prefer explicit prefixes. Either way the entrypoint logs any skipped (unmounted) user and warns when a pass finds zero scannable homes — the usual sign of a mount/prefix mismatch.
Environment
Kubernetes
Ready-to-apply manifests (namespace, configmap, secret.example, daemonset, cronjob, kustomization, plus a README) are available from your Runlayer account team — the same team that grants registry access. Each pod bind-mounts the node’s host user homes read-only under /host and attributes results to the node.
RUNLAYER_HOST, RUNLAYER_HOST_PASSWD=/host/etc/passwd, RUNLAYER_HOST_HOME_PREFIX=/host, and RUNLAYER_MACHINE_ID_PATH=/host/etc/machine-id values come from the ConfigMap; RUNLAYER_API_KEY comes from the Secret. All host mounts are readOnly: true under /host, matching those env paths.
DaemonSet vs CronJob — pick ONE
- DaemonSet (recommended): one long-running pod per node that loops on
RUNLAYER_SCAN_INTERVAL(default 900s). This is the only shape that gives fleet-wide per-node coverage automatically. Scope it withnodeSelector/tolerations. - CronJob: schedules a single
--onceJob every 15 min (concurrencyPolicy: Forbid). A CronJob does not run per-node — its pod lands on one scheduler-chosen node, so only that node is scanned each tick. Use it for single-node clusters, or pin it to a node vianodeSelector(one CronJob per node you want covered).
Node attribution
RUNLAYER_HOSTNAME is set from the downward API spec.nodeName, so scans attribute to the node rather than the ephemeral pod name. RUNLAYER_MACHINE_ID_PATH=/host/etc/machine-id points device-id derivation at the bind-mounted host machine-id, so the device id also matches the node.
The container runs as root (uid 0) so it can drop to each host user’s numeric uid for that user’s scan; it is not privileged and needs no host network or PID. It reads only the mounted files — never the Kubernetes API — so no RBAC is required (automountServiceAccountToken: false).
Device identity — don’t run two delivery models per host
The machine-id is the dedup key: AI Watch derives the device id from the host machine-id, so every user on a host maps to one device. Bind-mount/etc/machine-id (RUNLAYER_MACHINE_ID_PATH=/host/etc/machine-id) — or set RUNLAYER_DEVICE_ID explicitly — so the container’s device id matches the host.
Verify
On a test host, seed a shadow-AI config in a user’s home and run one pass:- Shadow AI → Devices shows the host with os = Linux, Detect = Active, Protect / Enforce / Sessions = Disabled (the correct Detect-only state).
- Connectors shows alice’s shadow MCP server.
kubectl -n runlayer logs on the DaemonSet pod (or the CronJob’s Job pod) shows a scanning user=... home=/host/home/... line per host user; the node appears in Shadow AI → Devices the same way.
Related
Linux (Detect only)
Native
.deb / .rpm packages, supported distributions, and cron model.Deploy AI Watch
Full deployment overview across macOS, Windows, and Linux.