LANList: A Minimal Local Device Presence Map (mDNS + Tiny Web UI)
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)
- Update your Pi and install dependencies:
sudo apt update && sudo apt upgrade -y - Install pip packages:
python3 -m pip install --user zeroconf flask - 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
- Create a systemd unit
/etc/systemd/system/lanlist.servicewith:[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 - 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)
- Add a tiny heartbeat: poll a few IPs with ICMP to show online/offline status.
- Cache results to disk every minute so a reboot doesn't leave you empty-handed for a bit.
- 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.