Files
NetAlertX/server/plugins/dockerdisc
Mauricio CamayoandClaude Sonnet 5 b175a3b65f Address jokob-sk review: shorten UI description, map scanSourcePlugin
Settings-UI description trimmed to one short line - implementation
detail (Socket Proxy, column mapping, CREATE_DEV behavior) already
lives in README, doesn't belong in the Settings page string.

scanSourcePlugin now maps to a static "DOCKERDISC" value (same
Dummy-column pattern arp_scan already uses), so a container device
created by this plugin gets devSourcePlugin set correctly instead of
NULL - every other CurrentScan-mapped plugin already does this.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011meLPKCzVpdZyAUfv5U6mm
2026-09-20 16:22:12 -05:00
..

Overview

DOCKERDISC enriches Docker hosts NetAlertX already knows about with the list of containers running on them - image, Compose project/service, network driver, and (for containers on a macvlan/ipvlan network) their own MAC/IP.

It never creates the Docker host itself as a device - that must already exist in NetAlertX before this plugin can attach anything to it. A container, on the other hand, can optionally get its own device too: opt in with DOCKERDISC_CREATE_DEV and a container on a macvlan/ipvlan network (its own LAN-visible MAC) can create or confirm a device of its own, parented to its host. A container without its own MAC (bridge/overlay/etc.) never creates one, regardless of that setting - there's no LAN-visible identity to create a device from.

Maintainer's mental model for this plugin: Device = Docker host → List of containers. Every container found on a host shows up under that host's own Device Details → Plugins → DOCKERDISC tab either way - opting a container into its own device is additive, it doesn't remove it from the host's list.

Tip

Connects via a read-only Docker Socket Proxy (e.g. tecnativa/docker-socket-proxy) - never mounts /var/run/docker.sock directly into the NetAlertX container.

Why a Socket Proxy, and not docker.sock directly?

Mounting /var/run/docker.sock into a container gives that container the same power as root on the host: anything that can reach the socket can, for example, start a new --privileged container with the host filesystem bind-mounted in - a standard, well-known way to escalate from "container access" to "host root." It can't be scoped down to "read-only" or "just these endpoints" - it's all or nothing.

That's a much bigger risk to accept for NetAlertX specifically than for a small single-purpose tool: NetAlertX is a web UI, a GraphQL API, and dozens of other plugins pulling in data from routers, DHCP leases, and other external sources - a large attack surface. A vulnerability anywhere in any of that would inherit full docker.sock access too, even though this plugin itself only ever needs to read three things: the container list, host info, and the network list.

The Socket Proxy sits between NetAlertX and the real socket and only forwards the specific API paths this plugin actually needs (CONTAINERS=1, INFO=1, NETWORKS=1) - everything else (exec, image builds, volumes, secrets, any POST that creates/kills something) is rejected by default. If NetAlertX is ever compromised, the blast radius stops at "can list containers/networks," not "can root the host."

Socket Proxy compose service

Add this as another service in the same docker-compose.yml as NetAlertX itself - not a separate stack/file:

services:
  netalertx:
    container_name: netalertx
    image: "ghcr.io/jokob-sk/netalertx"
    ... # same as you already have
    ...

  docker-socket-proxy:
    image: tecnativa/docker-socket-proxy:latest
    container_name: docker-socket-proxy
    environment:
      CONTAINERS: 1
      INFO: 1
      NETWORKS: 1
      POST: 0
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    restart: unless-stopped
    # Only if netalertx uses network_mode: host - see note below
    # ports:
    #   - "127.0.0.1:2375:2375"

Note

If your netalertx service uses network_mode: host (common, since ARP scanning needs a real host NIC), it won't resolve docker-socket-proxy by name - host networking means it isn't on the compose network at all. Fix: add ports: ["127.0.0.1:2375:2375"] to the proxy service above, and use http://127.0.0.1:2375 as the Socket Proxy URL instead. Don't give the proxy network_mode: host too - that likely exposes it to the whole LAN instead of just the NAS.

Same file, on purpose:

  • Compose puts every service in one file on the same default network automatically, so NetAlertX can reach it at http://docker-socket-proxy:2375 for free - no extra networks: config, no port published to the LAN (nothing else needs to reach it). (http://127.0.0.1:2375 only applies under the network_mode: host case above, not this default one - under default bridge networking each container has its own loopback, so 127.0.0.1 inside NetAlertX wouldn't reach the proxy container.)
  • Its lifecycle naturally follows NetAlertX's - one docker compose up/ down brings both up or down together, instead of a second stack to remember to manage separately.
  • It's a dependency this plugin needs, not an unrelated service, so it belongs with NetAlertX conceptually as well as operationally.

(The only real reason to split it into its own stack is sharing one proxy across several unrelated projects - e.g. Watchtower and NetAlertX both reading from the same proxy instead of each running their own. Not needed here.)

POST: 0 is already the image's default; it's listed explicitly since it's the setting that keeps this read-only - nothing here can create/start/stop/kill anything.

Quick setup guide

  1. Add the Socket Proxy service above to NetAlertX's docker-compose.yml and bring it up (docker compose up -d docker-socket-proxy) - one per Docker host you want tracked, if you're tracking more than one.
  2. In NetAlertX, add one entry per Docker host under Docker hosts (DOCKERDISC_hosts) with that proxy's URL (http://127.0.0.1:2375, http://docker-socket-proxy:2375, or whatever you named the service).
  3. Make sure the Docker host itself already exists as a device in NetAlertX (it normally does, found via ARP/Nmap - Docker hosts have a real NIC on the LAN). If host-MAC auto-detection doesn't find it (see below), fill in its MAC manually in the same entry.

Required Settings

  • When to run DOCKERDISC_RUN
  • Docker hosts DOCKERDISC_hosts - at least one entry, each with:
    • Docker Socket Proxy URL DOCKERDISC_SOCKET_PROXY_URL
    • Docker Host MAC Address (Fallback) DOCKERDISC_HOST_MAC - optional if auto-detection works for that host

Optional Settings

  • Create/confirm devices for containers with a real MAC DOCKERDISC_CREATE_DEV - off by default. A container without its own MAC (bridge/overlay/etc.) never creates a device either way; this only affects containers on a macvlan/ipvlan network.
  • Allow updating existing devices from this plugin's data DOCKERDISC_IMPORT_ON - on by default. Turn off to make a run purely informational: no CurrentScan promotion at all, so a container's own already-existing device (found independently by ARP/Nmap) won't get its presence/IP/parent fields updated from this plugin either, regardless of DOCKERDISC_CREATE_DEV. The two settings are independent - one doesn't gate the other.

Host MAC auto-detection

If DOCKERDISC_HOST_MAC is filled in, it's used immediately - no Socket Proxy call at all. Deliberate trade-off: a MAC is stable, so there's nothing to gain by re-confirming it via /info on every scheduled run, but it also means a future MAC change (e.g. a replaced NIC) won't be auto-detected while the field stays set.

Otherwise, the plugin calls the Socket Proxy's GET /info (Docker Engine API) to read the daemon's hostname, then looks for a NetAlertX device whose devName matches it. If either step fails - /info isn't reachable (check the INFO=1 permission), or no device's name matches - that host's entry is skipped for the run (logged, not fatal to other hosts).

Container listing

Every container on a host is listed, including bridge/overlay ones - not only macvlan/ipvlan containers. What changes per container is only whether it has a real LAN-visible identity to show:

  • Network Driver (watchedValue3) is always populated.
  • Container MAC (watchedValue4) and IP (extra) are populated only when the container has a macvlan/ipvlan network attached; otherwise they show null. A bridge-only container's own IP/MAC isn't LAN-visible, so there's nothing meaningful to show there - it still gets a row (image, Compose project/service, driver).
  • If a container is attached to more than one macvlan/ipvlan network at the same time (uncommon, but possible - e.g. a dual-homed network appliance), the network whose name sorts first alphabetically is the one shown. This is a deliberate, deterministic tie-break, not an attempt to pick the "right" one - Docker doesn't expose any ordering or priority between a container's networks, so any rule here is arbitrary; what matters is that it's stable (the same container always reports the same MAC/IP) rather than depending on whatever order the Socket Proxy's JSON happens to return them in.

Usage

  • Head to SettingsDocker discovery to configure Docker hosts.
  • Container details appear under each host's own Device DetailsPluginsDOCKERDISC tab.

Notes

  • The Docker host's own devMac/devLastIP/devFirstConnection/ devSourcePlugin/devCustomProps are never touched - ARP/Nmap remain authoritative for the host's identity and discovery-source attribution. A container with its own macvlan/ipvlan MAC always maps to CurrentScan (DOCKERDISC_IMPORT_ON permitting) - with DOCKERDISC_CREATE_DEV on, it can originate a brand-new device (devSourcePlugin set to DOCKERDISC at creation, like any other CurrentScan-mapped plugin); either way, if that MAC is already a device (found independently by ARP/Nmap, since it's LAN-visible), this plugin's row still confirms its presence and updates its devLastIP/devParentMAC on every run - that existing device's own devSourcePlugin isn't touched, since it's only set once, at creation. Turn DOCKERDISC_IMPORT_ON off to skip all of that and keep this plugin purely informational (its Plugins_Objects listing still updates either way) - see Plugin Import Behavior.

  • Only Socket Proxy permissions required: CONTAINERS=1 (list containers, their networks and labels), INFO=1 (host-MAC auto-detection), and NETWORKS=1 (network driver lookup - one batched GET /networks call per run for every unique network id seen, not one call per container). No write/exec permissions needed.

  • Design history and open implementation questions in [issue #1721] (https://github.com/netalertx/NetAlertX/issues/1721).

  • Version: 0.2.0

  • Author: mauricio-camayo

  • Release Date: 2026-09-18