NetAlertX network monitor quadlet

A LAN device scanner that alerts on new devices, using the host network and raw sockets.

  • Standalone container
  • Rootful: sudo systemctl
  • Tested with podman 5.0 and newer

Last updated

In short: NetAlertX finds devices with ARP scans, which need raw sockets on the host's real network interface. That means Network=host, the NET_RAW, NET_ADMIN and NET_BIND_SERVICE capabilities, and a rootful quadlet in /etc/containers/systemd/. Data lives in one /data folder with config and db subfolders owned by UID 20211, the image's service user. After the start, open port 20211 and set the subnets to scan.

The quadlet files

Every quadlet file below passes podman's quadlet generator and systemd-analyze verify on podman 5.0 and the current release. Personal values are placeholders: replace example.com, /srv/... paths and the time zone with your own.

netalertx.container

Save as /etc/containers/systemd/netalertx.container

[Unit]
Description=NetAlertX network monitor

[Container]
Image=ghcr.io/netalertx/netalertx:26.9.0
ContainerName=netalertx
# ARP scans need the host's real network interface.
Network=host
AddCapability=NET_RAW
AddCapability=NET_ADMIN
AddCapability=NET_BIND_SERVICE
Volume=/srv/netalertx:/data:Z
Volume=/etc/localtime:/etc/localtime:ro
Tmpfs=/tmp:uid=20211,gid=20211,mode=1700
Environment=PORT=20211

[Service]
Restart=always
TimeoutStartSec=900

[Install]
WantedBy=multi-user.target

Why is it built this way?

Why host networking. NetAlertX discovers devices with ARP scans, and ARP only reaches machines on the same layer-2 network. A container on podman's bridge would only see other containers, so Network=host gives it the host's real interfaces. With host networking there is no PublishPort=: the web UI on 20211 opens directly on the host.

Why rootful. Raw sockets on the host's interfaces need real capabilities. AddCapability=NET_RAW and NET_ADMIN in a rootless container only count inside its user namespace, not on the host network, so the ARP scan would fail. The file lives in /etc/containers/systemd/ and starts at boot.

Storage. Current releases keep everything in one /data folder with config and db subfolders, and run as the service user with UID and GID 20211. The quadlet bind-mounts /srv/netalertx there, mounts /etc/localtime so the timestamps match the host, and gives /tmp a Tmpfs= owned by that user, as the NetAlertX Docker guide prescribes.

Pinned version. netalertx:26.9.0 is an exact release without AutoUpdate=. The project has changed its container layout before, so read the release notes and update deliberately.

The trade-off. A rootful container on the host network with raw-socket capabilities has more reach than any other example on this site. Keep it on a trusted network, and do not expose port 20211 to the internet.

How do I start it?

Run these with sudo. Quadlet reads /etc/containers/systemd/ for system services.

sudo mkdir -p /etc/containers/systemd /srv/netalertx/config /srv/netalertx/db
# the image runs as UID/GID 20211
sudo chown -R 20211:20211 /srv/netalertx
# save netalertx.container in /etc/containers/systemd/, then:
sudo systemctl daemon-reload
sudo systemctl start netalertx.service
# open http://<server>:20211 and set SCAN_SUBNETS, e.g. 10.0.0.0/24 --interface=eth0

Check the result with sudo systemctl status and the logs with sudo journalctl -u followed by the service name. To turn a docker-compose file of your own into files like these, use the compose to quadlet converter.

Common mistakes

No devices are found after the first scan

The ARP scanner only scans the subnets in the SCAN_SUBNETS setting, each with its interface (10.0.0.0/24 --interface=eth0). Check the interface name with ip -br addr, and give the first discovery 5 to 10 minutes.

Permission denied or a read-only database in /data

The container runs as UID and GID 20211, so the host folder must belong to that ID: sudo chown -R 20211:20211 /srv/netalertx. The config and db subfolders must both exist and must not be nested.

arp-scan fails with Operation not permitted in a rootless container

Capabilities in a rootless container only apply inside its user namespace, not to the host's network interfaces. ARP scanning of the host LAN needs the rootful quadlet shown here.

Port 20211 is already in use on the host

With Network=host, the container's ports are host ports. Change the web UI port with Environment=PORT=, and the backend API port (20212) with the GRAPHQL_PORT setting.

Frequently asked questions

Why does NetAlertX need host networking?

ARP only works inside one layer-2 network. A bridged container sits on podman's own virtual network and would only see other containers, so the scanner must use the host's interfaces directly with Network=host.

Which ports does NetAlertX use?

Port 20211 for the web interface and 20212 for the backend API server. Because the container uses the host network, both are opened on the host itself; there is no PublishPort= in the quadlet.

Why is the NetAlertX image pinned to a version?

The image has changed its data layout before: older releases used /app/config and /app/db, current releases use one /data folder. Pinning the tag means you read the release notes and move data deliberately, instead of an automatic update breaking the mounts.

Where do I set the subnets NetAlertX scans?

In the web interface under Settings, in the ARP scan plugin's SCAN_SUBNETS field. You can also set it at start with Environment=APP_CONF_OVERRIDE= and a JSON object, as described in the NetAlertX Docker documentation.