Self-Hosted labwatch
Run the entire labwatch stack on your own hardware. One container, SQLite, no telemetry, your data never leaves your network. Same code as the managed service — AGPL-3.0 (server) + MIT (agent) licensed.
Why self-host?
Self-hosting is the right fit if any of these apply to you:
- Your homelab monitors air-gapped or sensitive infrastructure and you don't want metrics leaving your network.
- You want unlimited history and don't want to pay per-node.
- You already run a docker host and want to add one more stateless service.
- You want to extend or patch the code — the server is AGPL-3.0 and the agent is MIT licensed on GitHub.
If you'd rather not run another service, the managed tiers are cheaper than any VPS and include alerting, digests, and the natural-language query interface with zero setup.
Requirements
- Docker 20.10+ and docker compose v2 (or Podman with compose).
- A host reachable from every machine you want to monitor (LAN, VPN, or public).
- ~150 MB RAM idle, ~300 MB with 20+ nodes. Disk usage grows ~50 MB per node per year.
- Outbound internet not required after the initial image pull.
Quick start
Four commands and you're running.
1Clone the repo
2Set your secrets
Create a .env file next to docker-compose.yml:
Generate secrets with openssl rand -hex 24 and paste them as values. The admin secret is used to log in and register agents. The session secret signs user cookies (must be stable across restarts).
3Start the stack
This builds the image from source (takes ~60 seconds the first time) and starts labwatch on port 8097. By default it only listens on 127.0.0.1 — agents on other machines won't be able to reach it. Either change the port in docker-compose.yml to "8097:8097" or put a reverse proxy in front. Logs: docker compose logs -f labwatch.
4Log in
Open http://your-host:8097, click Log in, and paste the admin secret from your .env. You're in.
Environment variables
All configuration is done through environment variables — set them in .env next to the compose file, or inline in docker-compose.yml.
| Variable | Default | Purpose |
|---|---|---|
| ADMIN_SECRET | change-me | Admin login secret. Must be changed before first boot. Also used as the X-Admin-Secret header for the admin API. |
| BASE_URL | http://localhost:8097 | Public URL of your labwatch instance. Used in signup emails and the agent installer script. |
| DATABASE_PATH | /app/data/labwatch.db | SQLite database location inside the container. The default is mapped to the labwatch-data named volume. |
| PORT | 8097 | Port the FastAPI server listens on inside the container. |
| RETENTION_HOURS | 168 | How long to keep metrics, alerts, and heartbeat data. Older rows are pruned on startup and every 24h. Bump this if you want longer history (at the cost of disk). |
| SESSION_SECRET | auto-generated | Secret key for signing session cookies. If not set, a random key is generated on startup — meaning sessions are invalidated on every container restart. Set this to a fixed value (openssl rand -hex 32) if you want sessions to survive restarts. |
Reverse proxy & HTTPS
For anything beyond a single-machine LAN setup you'll want HTTPS. A minimal Caddy example:
Caddy handles ACME automatically. For nginx or Traefik, proxy to 127.0.0.1:8097 and make sure X-Forwarded-For / X-Forwarded-Proto headers are preserved. Update BASE_URL in your .env to the public HTTPS URL and restart the container.
Backups
All state lives in a single SQLite file at /app/data/labwatch.db (mapped to the labwatch-data volume). Back it up the same way you back up anything else:
Or just snapshot the Docker volume directly with restic, borg, or whatever you already use.
Install agents
Once the server is running, install the agent on each machine you want to monitor. The installer pulls the latest binary from your labwatch instance — no external dependency:
The script installs the agent to /usr/local/bin/labwatch, creates a systemd unit, and drops an empty config at /etc/labwatch/config.yaml. Prefer to review scripts before running? Download it first:
Agent config
Edit /etc/labwatch/config.yaml on the agent host. The minimum required fields are api_endpoint, admin_secret (one-time, for registration), and a collection interval:
Register with the server, then start the service:
After a successful register the agent prints your lab_id and token to stdout. Paste them into /etc/labwatch/config.yaml, then you can remove admin_secret from the config.
lab_id and token.
Upgrading
Pull the latest source and rebuild:
Your database volume is preserved across rebuilds. Schema migrations run automatically on startup.
License FAQ
The server is AGPL-3.0 and the agent is MIT. Here's what that means in practice:
I run labwatch on my LAN for myself. Does AGPL affect me?
No. AGPL's sharing requirement only triggers when you provide the software as a network service to third parties. Running it on your own LAN for your own use has zero obligations beyond the license grant you already received.
Can I embed the agent in my own project?
Yes. The agent is MIT — no restrictions. Embed it, fork it, ship it commercially, whatever you need.
What if I modify the server and host it for my team?
If you modify the server and let others access it over a network, AGPL requires you to make your modified source available to those users. The simplest way: push your fork to a public repo and link to it from the UI.
Does the managed tier (labwatch.dev) use different code?
Same codebase. The managed tier adds hosting, backups, and support — no proprietary code on top.
Troubleshooting
Container won't start
Check logs with docker compose logs labwatch. The most common causes are a port conflict on 8097 (change the left side of the port mapping) or a volume permission issue (remove the volume with docker volume rm labwatch_labwatch-data and restart — this wipes data).
Agents register but no metrics appear
Check the agent logs: journalctl -u labwatch -f. If you see 401 Unauthorized, the token in /etc/labwatch/config.yaml is wrong — re-run labwatch --register. If you see connection refused, verify api_endpoint is reachable from the agent: curl http://your-labwatch-host:8097/health.
NLQ returns “I didn’t quite get that”
The query bar uses regex pattern matching — it recognizes ~20 query shapes (fleet overview, alerts, cpu/memory/disk pressure, containers, temperature, network, nodes needing attention). Rephrase toward one of those shapes, or try clicking a suggested chip. No API key is needed — if you got an error, check docker compose logs labwatch for a stack trace.
Still stuck? Email support@labwatch.dev with your docker compose logs labwatch output and we'll dig in with you.