Keep /alive unchanged and serve a dashboard at / with status cards, latency graphs, an event timeline, and a Wi-Fi section. Background monitors on configurable intervals: TCP reachability, DNS resolution, public IP change detection, host reboot detection, Wi-Fi link quality, and Wi-Fi scan of networks in range via iw. The Wi-Fi interface is selected with WIFI_INTERFACE; the page lists visible interfaces and reports when none is available. Scan results stay in memory, other metrics go to SQLite on a mounted volume. Optional MQTT publishing with Home Assistant discovery groups all sensors under one device per host. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TdvJYoY8kc8bmBck89U2i6
healthcheck-container
Small Flask service for remote devices (Raspberry Pi in Docker) that answers
/alive for container monitoring and, on top of that, watches the network the
device sits on: internet reachability, DNS, public IP changes, Wi-Fi link
quality, Wi-Fi networks in range, and host reboots. Metrics go to SQLite and
are shown on a dashboard at /. Optionally the same values are published to an
MQTT broker with Home Assistant discovery, so each device shows up in Home
Assistant as a device with sensors and no manual configuration.
Endpoints
| Path | Purpose |
|---|---|
/alive |
Unchanged health check, returns {"alive": true} |
/ |
Dashboard: status, Wi-Fi, graphs, timeline |
/api/status |
Latest state of every monitor as JSON |
/api/wifi |
Interface list, link details, last scan results |
/api/events?limit=200 |
Timeline events, newest first |
/api/samples?prefix=reach.&range=3600 |
Bucketed samples for graphs |
Running
docker compose up -d --build
Then open http://<device>:9999/.
Wi-Fi needs two things from Docker:
network_mode: host, otherwise the container has no wireless interface at all.cap_add: NET_ADMIN, otherwise link and interface info work but scanning fails with a permission error that is shown on the dashboard.
Without WIFI_INTERFACE the dashboard lists the interfaces it can see and
nothing is scanned. Set the variable to one of the listed wireless interfaces
and restart the container.
Storage
Samples and events are stored in SQLite at DB_PATH (default
/data/healthcheck.db). The compose file mounts a named volume at /data, so
the database survives docker compose down, image rebuilds and up -d. Only
docker compose down -v or docker volume rm deletes it. A bind mount works
as well, for example - /opt/healthcheck:/data.
Samples older than SAMPLE_RETENTION_DAYS (default 7) are deleted hourly.
Events are kept. Wi-Fi scan results are held in memory only and never written.
Configuration
All settings are environment variables. Intervals are in seconds.
| Variable | Default | Meaning |
|---|---|---|
PORT |
9999 |
HTTP port |
DB_PATH |
/data/healthcheck.db |
SQLite file |
SAMPLE_RETENTION_DAYS |
7 |
How long graph samples are kept |
DEVICE_NAME |
hostname | Shown on the dashboard and in Home Assistant |
DEVICE_ID |
slug of DEVICE_NAME |
Used in MQTT topics and unique ids |
REACH_TARGETS |
1.1.1.1:443,8.8.8.8:443,9.9.9.9:443 |
TCP connect targets, host:port |
REACH_INTERVAL |
30 |
|
REACH_TIMEOUT |
3 |
Connect timeout per target |
DNS_NAME |
cloudflare.com |
Name resolved through the system resolver |
DNS_INTERVAL |
60 |
|
PUBLIC_IP_URLS |
ipify, ifconfig.me, icanhazip | Tried in order, first answer wins |
PUBLIC_IP_INTERVAL |
300 |
|
PUBLIC_IP_TIMEOUT |
5 |
|
UPTIME_INTERVAL |
60 |
Reboot detection via /proc/uptime |
WIFI_INTERFACE |
empty | Wireless interface to use, empty disables Wi-Fi |
WIFI_LINK_INTERVAL |
30 |
Signal, bitrate, retries of the current link |
WIFI_SCAN_INTERVAL |
60 |
Scan for networks in range |
MQTT_HOST |
empty | Broker host, empty disables MQTT |
MQTT_PORT |
1883 |
|
MQTT_USERNAME |
empty | |
MQTT_PASSWORD |
empty | |
MQTT_DISCOVERY_PREFIX |
homeassistant |
Must match the MQTT integration setting |
LOG_LEVEL |
INFO |
Events on the timeline
- Internet unreachable / reachable again (all targets failed, then any succeeded)
- DNS resolution failed / working again
- Public IP is X, Public IP changed from X to Y
- Wi-Fi connected, disconnected, roamed to another BSSID
- Host rebooted
- Health check service started
Home Assistant
Set MQTT_HOST (and credentials if the broker needs them). On connect the
service publishes retained discovery messages under
homeassistant/<component>/healthcheck_<device_id>/<key>/config, all pointing
to one device, then publishes state to healthcheck/<device_id>/state and
availability to healthcheck/<device_id>/availability. When Home Assistant
restarts it announces itself on homeassistant/status and the service
re-publishes discovery.
Entities per device:
- Internet, DNS, Wi-Fi (binary sensors,
connectivityclass) - Internet latency, DNS latency (ms)
- Public IP
- Host uptime
- Wi-Fi SSID, BSSID, channel, signal (dBm), TX bitrate (Mbit/s), networks in range
Development
python3 -m unittest discover -s tests
DB_PATH=./data/hc.db python3 app.py