← Home Few Bits
Cover image for LANList: A Minimal Local Device Presence Map (mDNS + Tiny Web UI)

LANList: A Minimal Local Device Presence Map (mDNS + Tiny Web UI)

Alex Solis Alex Solis ·

What and why

LANList is a tiny, local-only presence map for your home or workshop network. It discovers devices that advertise via mDNS/Bonjour, aggregates their names, IPs and service types, and serves a tiny static web page you can open on any device on the LAN. It's deliberately minimal: low CPU and memory, no third-party services, no accounts, and a small codebase you can read in one sitting.

What you need

  • A small always-on Linux host. Raspberry Pi Zero W is perfect, but any Pi, an old laptop, or a tiny VM works.
  • Python 3.8+ (usually preinstalled) and pip.
  • Network access to the devices you want to see — mDNS only works on the same link unless your router forwards it.

Install (fast path)

  1. Update your Pi and install dependencies: sudo apt update && sudo apt upgrade -y
  2. Install pip packages: python3 -m pip install --user zeroconf flask
  3. Create a project folder: mkdir -p ~/lanlist && cd ~/lanlist

Minimal code

This is intentionally tiny. Create a file lanlist.py with the following contents:

from zeroconf import ServiceBrowser, Zeroconf from flask import Flask, jsonify, render_template_string import threading services = {} class Listener: def remove_service(self, zc, type_, name): services.pop(name, None) def add_service(self, zc, type_, name): info = zc.get_service_info(type_, name) if info: ips = ["%d.%d.%d.%d" % tuple(b) for b in info.addresses] services[name] = { 'name': name, 'type': type_, 'ips': ips, 'port': info.port, } zc = Zeroconf() listener = Listener() ServiceBrowser(zc, "_services._dns-sd._udp.local.", listener) app = Flask(__name__) TEMPLATE = '''LANList

LANList — local devices

    {% for s in services %}
  • {{s['name']}} — {{s['type']}} — {{', '.join(s['ips'])}} :{{s['port']}}
  • {% endfor %}
Updated live from mDNS. No external calls.''' @app.route('/') def index(): return render_template_string(TEMPLATE, services=sorted(services.values(), key=lambda s: s['name'])) @app.route('/json') def api(): return jsonify(sorted(services.values(), key=lambda s: s['name'])) if __name__ == '__main__': # Run Flask in a background thread to keep zeroconf running in main thread threading.Thread(target=lambda: app.run(host='0.0.0.0', port=8080), daemon=True).start() try: input('LANList running. Press Enter to stop.\n') finally: zc.close()

Save it and run with: python3 ~/lanlist/lanlist.py. Point a browser to http://your-pi-ip:8080/ and you should see a simple list of discovered devices.

How it works (brief)

  • zeroconf library listens for mDNS updates and keeps a dictionary of services.
  • Flask serves a minimal UI and a JSON endpoint. The UI is static and rendered on request — no JS required.
  • All discovery and serving stay local to your LAN.

Deploy as a service

  1. Create a systemd unit /etc/systemd/system/lanlist.service with: [Unit] Description=LANList After=network-online.target [Service] User=pi WorkingDirectory=/home/pi/lanlist ExecStart=/usr/bin/python3 /home/pi/lanlist/lanlist.py Restart=on-failure [Install] WantedBy=multi-user.target
  2. Enable and start: sudo systemctl daemon-reload && sudo systemctl enable --now lanlist

Troubleshooting

  • If nothing appears, confirm that devices actually advertise mDNS. Many IoT devices do; others (some printers, routers) may not.
  • Check network isolation features on your router. Guest networks often block mDNS.
  • Run python3 -c 'from zeroconf import Zeroconf; z=Zeroconf(); print(z.get_addresses())' to ensure zeroconf initializes without error.
  • If you see only IPv6 addresses or empty address lists, your devices might prefer IPv6 or the Pi has IPv6-only configured. Adjust the code to handle IPv6 if needed.

Small enhancements (pick one)

  1. Add a tiny heartbeat: poll a few IPs with ICMP to show online/offline status.
  2. Cache results to disk every minute so a reboot doesn't leave you empty-handed for a bit.
  3. Serve a compact favicon and tiny CSS from the same app for a cleaner UI.

Privacy and limits

LANList deliberately keeps data local. It does not phone home, log to cloud services, or require accounts. But be mindful: the dashboard shows device hostnames and IPs visible on your LAN — treat it like any other internal inventory. If you expose this port beyond your LAN (don't), you leak that inventory.

Tip: Use a firewall or router rule to restrict access to the Pi's 8080 port if you need tighter control.

Closing

LANList is designed to be simple and useful. It's not a full network scanner or home assistant replacement — it's a small, transparent tool that gives a quick answer to the question: what's advertising itself on my LAN right now? You can extend it as you like, but starting with a tiny, readable codebase keeps things debuggable and privacy-friendly.

If you build something neat on top of this — a tension-free upgrade path is to add tiny icons, group devices by vendor, or push a local notification when a specific device arrives — drop a note in your project log and keep the stack minimal.