LibreNMS provides a method of performing network inventory, monitoring, and aggregation in the open-source way. With a GPLv3 license, LibreNMS ensures that we can escape the possibility of closed-source enshittifcation, at least with this software offering.
This is my method of deploying it on my homelab.
LibreNMS Architecture
LibreNMS is developed in a modular method, as much software is these days, requiring separate deployments of a backend, frontend, and various (optional) sidecars to add functionality.
At a minimum, LibreNMS requires:
- A MySQL-compliant database
- The LibreNMS application frontend
- A redis (or valkey) queue manager
- A “dispatcher” sidecar, controlling polling operations, etc.
Frequently, it is deployed with the additional sidecars:
- msmtpd — for email notifications
- snmptrapd — for collecting SNMP information
My deployment will feature all available sidecars, to ensure that readers have the opportunity to see a full-coverage deployment. You may choose to omit the msmtpd or snmptrapd sidecars if you do not require them.
Tip
You do not need the snmptrapd sidecar to enable SNMP data collection. LibreNMS can add devices via SNMP connections on port 161/UDP. snmptrapd is only needed if you are configuring your devices to push data to the application deployment via port 162/UDP. Traps transmit information. SNMP alone polls information.
My deployment strategy here, as many of my others do, will rely on containers and Podman. We will use systemd as our container orchestrator through the use of Quadlets.
This will work on any operating system that supports Podman 3.4.0 or newer. That requirement comes down to when dynamic systemd unit generation through quadlets was added. I’ll be setting this up on OpenSUSE MicroOS.
Note
If you are not familiar with quadlets and feel the need to dive in a bit deeper than what is shown in this post, feel free to check out this post on headscale in quadlets for some background.
The Basic Deployment
I have chosen to deploy this with a shared network namespace amongst containers rather than in a pod, as I had trouble with the application talking to the database on localhost. In fact, even with a shared network, referencing the application by name still required a manually modification of database user ACLs to function. That’s a one-time step though and we’ll tackle it when we get there.
We’ll start with the database container. This will be a MariaDB image, as that’s the only thing that LibreNMS supports. It’s a shame there is no Postgres support, but maybe at a later day…
Setup the supporting infrastructure
First we’ll create our network quadlet file:
cd /etc/containers/systemd
echo '[Network]' | sudo tee lnms.networkNext we’ll create the persistent volume for the database backend:
echo '[Volume]' | sudo tee lnms-db.volumeSetup the container itself
For the container itself, our configuration file has some more content… Copy and paste the following container quadlet definition into a file call lnms-db.container:
[Unit]
Description=Libre NMS Database Container
After=network-online.target
[Container]
ContainerName=lnms-db
Image=docker.io/mariadb:11
Volume=lnms-db.volume:/var/lib/mysql
Environment=TZ=America/New_York #Replace this with your timezone
Environment=MARIADB_ROOT_PASSWORD_FILE=/run/secrets/db_root_password
Environment=MARIADB_USER=lnms
Environment=MARIADB_DATABASE=lnms
Environment=MARIADB_PASSWORD_FILE=/run/secrets/db_password
Exec=--innodb-file-per-table=1 --lower-case-table-names=0 --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
Secret=lnms_db_password,type=mount,target=/run/secrets/db_password
Secret=lnms_db_root_password,type=mount,target=/run/secrets/db_root_password
AutoUpdate=registry
Network=lnms.network
[Service]
Restart=always
[Install]
WantedBy=default.targetNote
Note the use of podman secrets here. This is a (better) alternative way of passing sensitive information into a container programmatically to the use of environment variables. Secrets support file mount or environment variables though, for those applications that haven’t engineered in support for the use of container secrets. We’ll see that with the frontend container.
Setup the secrets
Two secrets are referenced in our database container: one for a DB user password, and one for the DB root password. We’ll create those here.
Unlike docker secrets in docker-compose manifests, where the cleartext value lives on disk in a file, we’re going to create our podman secrets without a file-backed reference. We will temporarily create files with the cleartext value only to ingest into the podman secret, as that keeps the keystrokes out of bash history and STDIN as a pipe.
Secret Creation
- Create a file called
db_passwordand use your favorite TUI text editor to populate it with a password. Save this in your password manager case you need it later, we’ll be deleting this file securely.
nvim db_password
---
A sup3r secure passw0rd here!
---
Esc
:wqEnsure your secret does not contain any newlines though, otherwise they will become part of the password.
- Referencing the file create the podman secret, note that the secret name is the important part here.
sudo podman secret create lnms_db_password ./db_password- Repeat with another secret:
lnms_db_root_passwordand file. - Securely delete the two files:
shred -u db_password
shred -u db_root_passwordTip
The shred utility unlinks (deletes) a file when the -u switch is included. Prior to removing it, random data is used to overwrite the file contents. Otherwise, when a file is deleted it is simply unlinked, the file descriptor removed, but the data still lives on disk unallocated. This makes it possible for forensic processes to recover the data of the deleted file.
Start the database container
First, tell systemd to reread config files from disk, to have our quadlets generated into systemd units:
sudo systemctl daemon-reloadNext, start your fresh units and check the logs:
sudo systemctl start lnms-network lnms-db-volume lnms-db
sudo journalctl -u lnms-db -n50 -fWatch the log output until it stabilizes after first launch, where it bootstraps the database, then hit CTRL+C to escape the log stream.
One down, a few to go! They’ll all be similar though.
Setup the Redis/Valkey container
Next we’ll address our queueing container. I’ve chosen to use Valkey here as an alternative to Redis. While redis recently declared their community offering open-source again, the history of the project draws into question the intent of developers and good-will for the community. Valkey is the fork that was created as soon as Redis initially went proprietary to maintain the open-source community mindset. I support that. It’s a drop-in replacement here too, which makes it a no-brainer in my opinion.
Only one file for this one, copy/paste the following into your lnms-valkey.container file:
[Unit]
Description=Libre NMS ValKey Container
After=network-online.target
[Container]
ContainerName=lnms-valkey
Image=docker.io/valkey/valkey:7-alpine
Environment=TZ=America/Timbuktu #Replace with your timezone
AutoUpdate=registry
Network=lnms.network
[Service]
Restart=always
[Install]
WantedBy=default.targetStart-up the container:
sudo systemctl daemon-reload
sudo systemctl start lnms-valkeySetup the application frontend container
We’ll first get the application going, then move on to the dispatcher container.
This one needs persistent storage again, so we’ll first create a volume:
echo '[Volume]' | sudo tee lnms.volumeNext we’ll create the container quadlet. Copy and paste the following into a file called lnms-app.container:
[Unit]
Description=Libre NMS Frontend Container
After=network-online.target lnms-db.service lnms-valkey.service
Requires=lnms-db.service lnms-valkey.service
[Container]
ContainerName=librenms
Image=docker.io/librenms/librenms:latest
Volume=lnms.volume:/data
Environment=TZ=America/WhereAmI #Update to your Timezone
Environment=DB_HOST=lnms-db
Environment=DB_NAME=lnms
Environment=DB_USER=lnms
Environment=DB_TIMEOUT=60
Secret=lnms_db_password,type=env,target=DB_PASSWORD
Environment=MEMORY_LIMIT=256M
Environment=MAX_INPUT_VARS=1000
Environment=UPLOAD_MAX_SIZE=16M
Environment=OPCACHE_MEM_SIZE=128
Environment=REAL_IP_FROM=0.0.0.0/32
Environment=REAL_IP_HEADER=X-Forwarded-For
Environment=LOG_IP_VAR=remote_addr
Environment=CACHE_DRIVER=redis
Environment=SESSION_DRIVER=redis
Environment=REDIS_HOST=lnms-valkey
Secret=lnms_snmp_community,type=env,target=LIBRENMS_SNMP_COMMUNITY
Environment=LIBRENMS_WEATHERMAP=true
Environment=LIBRENMS_WEATHERMAP_SCHEDULE='*/5 * * * *'
PublishPort=8000:8000
AddCapability=NET_ADMIN NET_RAW
Network=lnms.network
AutoUpdate=registry
[Service]
Restart=always
[Install]
WantedBy=default.targetThis container references two secrets again, but this time they are the secrets for the DB user password, and your “default” SNMP community string. We already have the first, we have to create the latter. Follow the same process as above to create a secret for your SNMP community string. Make sure you call it lnms_snmp_community, or update your quadlet file accordingly.
Note
This time the secrets are of type=env. This takes the secret value and populates an environment variable with it, rather than a file in the /run/secrets directory. The latter is the preferred method, as access to ACL-controlled files is more strict than environment variables, but unfortunately here LibreNMS doesn’t support reading secrets from files, so we’re forced to use the environment variable method. This still keeps the cleartext value out of our local files on disk though.
Start the container:
sudo systemctl daemon-reload
sudo systemctl start lnms-app
journalctl -u lnms-app -n50 -fYou’ll likely see the database migration step fail because the lnms user doesn’t have appropriate permissions. We’ll fix that here.
We need to grant permissions on all IPs to the database user using a SQL statement, then we’ll restart our app container:
sudo podman exec -it lnms-db mariadb -e "GRANT ALL ON *.* TO 'lnms'@'%'; FLUSH PRIVILEGES;"
sudo systemctl restart lnms-appNow if you check the logs, you should see the container initialize correctly.
Setup the dispatcher sidecar
One last container for our core deployment!
In a file called lnms-dispatcher.container, copy and paste the following:
[Unit]
Description=Libre NMS Dispatcher Container
After=network-online.target lnms-db.service lnms-valkey.service
Requires=lnms-db.service lnms-valkey.service
[Container]
ContainerName=librenms_dispatcher
Image=docker.io/librenms/librenms:latest
Volume=lnms.volume:/data
Environment=TZ=OuterSpace/Mars #Your Timezone here
Environment=DB_HOST=lnms-db
Environment=DB_NAME=lnms
Environment=DB_USER=lnms
Environment=DB_TIMEOUT=60
Secret=lnms_db_password,type=env,target=DB_PASSWORD
Environment=DISPATCHER_NODE_ID=dispatcher1
Environment=SIDECAR_DISPATCHER=1
Environment=MEMORY_LIMIT=256M
Environment=MAX_INPUT_VARS=1000
Environment=UPLOAD_MAX_SIZE=16M
Environment=OPCACHE_MEM_SIZE=128
Environment=REAL_IP_FROM=0.0.0.0/32
Environment=REAL_IP_HEADER=X-Forwarded-For
Environment=LOG_IP_VAR=remote_addr
Environment=CACHE_DRIVER=redis
Environment=SESSION_DRIVER=redis
Environment=REDIS_HOST=lnms-valkey
Secret=lnms_snmp_community,type=mount,target=/run/secrets/lnms_snmp_community
Environment=LIBRENMS_WEATHERMAP=true
Environment=LIBRENMS_WEATHERMAP_SCHEDULE="*/5 * * * *"
AddCapability=NET_ADMIN NET_RAW
Network=lnms.network
AutoUpdate=registry
[Service]
Restart=always
[Install]
WantedBy=default.targetThis is effectively the same container as the frontend application, but it contains some ENV vars to tell it that it is a sidecar, and the main app can then call it for functionality.
Start it up, and we’ll be ready to check our webUI.
sudo systemctl daemon-reload
sudo systemctl start lnms-dispatcherHead over to http://you.IP.address.Here:8000/ in your browser and you’ll be greeted with the sign-in screen!

Where are my creds?!
We now have to create an admin user to login with. Back in your terminal fire off the following command and follow the prompts for password creation.
sudo podman exec -it librenms /opt/librenms/lnms user:add -r admin YOURUSERNAMEAfter you complete that process you can go sign in!
Extended Functionality
If you have no desire for email alerting or snmptrap functionality, feel free to skip this portion!
You’ll need two more .container files for these two units, and supporting environment variables or secrets. Here are the container file defitions:
lnms-snmptrapd.container
[Unit]
Description=Libre NMS SNMP Container
After=network-online.target lnms-db.service lnms-valkey.service
Requires=lnms-db.service lnms-valkey.service
[Container]
ContainerName=librenms_snmptrapd
Image=docker.io/librenms/librenms:latest
Volume=lnms.volume:/data
Environment=TZ=America/ImLost #Update your timezone
Environment=DB_HOST=lnms-db
Environment=DB_NAME=lnms
Environment=DB_USER=lnms
Environment=DB_TIMEOUT=60
Secret=lnms_db_password,type=env,target=DB_PASSWORD
Environment=SIDECAR_SNMPTRAPD=1
Environment=MEMORY_LIMIT=256M
Environment=MAX_INPUT_VARS=1000
Environment=UPLOAD_MAX_SIZE=16M
Environment=OPCACHE_MEM_SIZE=128
Environment=REAL_IP_FROM=0.0.0.0/32
Environment=REAL_IP_HEADER=X-Forwarded-For
Environment=LOG_IP_VAR=remote_addr
Environment=CACHE_DRIVER=redis
Environment=SESSION_DRIVER=redis
Environment=REDIS_HOST=lnms-valkey
Secret=lnms_snmp_community,type=mount,target=/run/secrets/lnms_snmp_community
Environment=LIBRENMS_WEATHERMAP=true
Environment=LIBRENMS_WEATHERMAP_SCHEDULE="*/5 * * * *"
PublishPort=162:162
PublishPort=162:162/udp
AddCapability=NET_ADMIN NET_RAW
Network=lnms.network
AutoUpdate=registry
[Service]
Restart=always
[Install]
WantedBy=default.targetlnms-msmtpd.container
[Unit]
Description=Libre NMS MSMTPD Container
After=network-online.target
[Container]
ContainerName=lnms-msmtpd
Image=docker.io/crazymax/msmtpd:latest
Environment=TZ=America/Somewhere #Update your timezone
Environment=SMTP_HOST=mail.yourdomain.tld
Environment=SMTP_PORT=587
Environment=SMTP_TLS=on
Environment=SMTP_STARTTLS=on
Environment=SMTP_TLS_CHECKCERT=on
Environment=SMTP_AUTH=on
Environment=SMTP_USER=librenms@yourdomain.tld
Environment=SMTP_FROM=librenms@yourdomain.tld
Secret=lnms_smtp_password,type=env,target=SMTP_PASSWORD
AutoUpdate=registry
Network=lnms.network
[Service]
Restart=always
[Install]
WantedBy=default.targetDon’t forget to create your secret for the SMTP password value! You’ll also have to configure that user/mailbox on your mail provider’s end too, of course.
Conclusion
You’ve done it! You have LibreNMS running in podman quadlets, ready to be configured to ingest data from all over your network.
Consider a few tips to make the most of it:
Tip
Anywhere your devices support it, opt for SNMPv3! Until v3, all SNMP was technically unauthenticated, using only community strings as shared secrets. While this might sound fine, these values are transmitted on the wire in cleartext. Any method of establishing a machine-in-the-middle position puts these community strings at risk, in direct contrast to SNMPv3 where there is proper authentication and encryption, and none of it is sent in cleartext.
- Use a long, random SNMP community string on all your devices that is the same everywhere, and configure that value in the LibreNMS dispatcher for autodiscovery, assuming your devices support only SNMPv2c.
- Place your LibreNMS deployment on an administrative network that has traffic permitted to all internal network segments on at least ICMP and SNMP
- Manually add devices for your firewall, core switches, access switches, and wireless APs to get data on the bulk of your network.
- Consider adding a cron job on the host to periodically run
podman exec -i librenms bash -c "/opt/librenms/snmp-scan.py -t 5 --ping-fallback && exit" >/dev/null 2>&1which will perform automatic discovery including ping-only discovery. This should get you the rest of your hosts that can’t be mapped via SNMP directly, or attribution through another network appliance providing data on its connection.
After that it’s just customizing and tweaking to your liking. The LibreNMS documentation are a fantastic place to start.
Here’s an example of some of the cool bandwidth usage monitoring you can do.

Happy Hacking!
Support
If you’ve enjoyed this, consider helping support the infrastructure to run it.
Buy me a coffee or three with the button below.
Comments
Nothing yet. Say the first thing.
Sign in to join the conversation.