The Alerts page's active list now says each alert in one line around the resource it watches, e.g. "cpu_pct on web-1 is 94% (> 90%), firing for 12m", with web-1 linking to its host, monitor or container page (or the plugin's page for Traefik, UniFi and SNMP). Values take their unit from the metric name through formatting.metric_value. A silent series reads "No cpu_pct from web-1 for 20m"; an up/down metric says the word. Actions follow the routes' roles: acknowledge, edit, toggle and New rule for operators and admins, Delete for admins only, nothing for viewers. The sidebar shows how many alerts need attention beside Alerts, from an async context processor that skips htmx fragments, logged-out pages and the database-down page, and renders without the count if the query fails. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Steward
A self-hosted network monitoring and infrastructure management hub for home servers. Steward gives you a single pane of glass over your hosts, services, and automation — with live-updating dashboards, alerting, and Ansible integration.
What It Does
- Ping monitoring — TCP/ICMP probes with live latency history and configurable thresholds
- DNS monitoring — resolution checks with optional expected-IP validation
- Ansible — browse playbooks, trigger runs, stream output live
- Alerting — threshold-based rules against any monitored metric, with email and webhook (Discord-compatible) notifications
- Plugins — extend with additional data sources; Traefik metrics included out of the box
- Dashboard widgets — all monitors and plugins contribute live-updating widgets
No JavaScript framework. No build step. No external workers or message brokers.
Quick Start — Docker
cp .env.example .env
# Set STEWARD_DATABASE_URL in .env
docker compose up -d
Open http://localhost:5000. On first run you'll be prompted to create the admin account.
This compose file builds the image locally and bind-mounts the source for live editing. To run a persistent instance off the published CI image instead, see Remote dev instance below.
Remote dev instance
For a long-lived instance on a remote server that tracks the dev CI line, use compose.deploy.yml. It pulls the published image (git.fabledsword.com/bvandeusen/steward:dev) rather than building, runs no source bind-mounts, and persists data in named volumes.
One-time prerequisite — registry push token (on the Git host). CI publishes the image, so the FabledSteward repo needs a push credential. The injected GITHUB_TOKEN lacks write:package, so create a Forgejo token scoped read:package + write:package and add it as the repo Actions secret REGISTRY_TOKEN. Until this exists, the publish CI job 401s and no :dev image is produced.
Standup (on the server):
# 1. Authenticate to the registry (read:package token is enough to pull)
docker login git.fabledsword.com -u <user>
# 2. Configure secrets
cp .env.example .env
# Set POSTGRES_PASSWORD. Leave STEWARD_SECRET_KEY unset to let the app
# generate + persist one on the /data volume.
# 3. Pull + boot
docker compose -f compose.deploy.yml up -d
Open http://<server>:5000 and create the admin account on first run.
Update to the latest dev build:
docker compose -f compose.deploy.yml pull
docker compose -f compose.deploy.yml up -d
Every push to dev that goes CI-green publishes a fresh :dev (and an immutable :<commit-sha> for rollback). To pin a specific commit, change the image: tag in compose.deploy.yml to :<sha>.
Quick Start — Bare Metal
Requires Python 3.11+ and PostgreSQL.
python -m venv .venv && source .venv/bin/activate
pip install -e .
cp config.example.yaml config.yaml
# Edit config.yaml — set database.url at minimum
steward --host 0.0.0.0 --port 5000
ICMP ping requires CAP_NET_RAW or the setuid ping binary. TCP mode (the default) needs no elevated privileges.
Configuration
Only two things must be set to run the app:
| What | How |
|---|---|
| Database URL | STEWARD_DATABASE_URL env var or database.url in config.yaml |
| Secret key | Auto-generated on first run and saved to /data/secret.key |
Everything else — SMTP, webhooks, monitor intervals, Ansible sources, plugin settings — is configured through the web UI at /settings/.
Plugins
Drop a directory into plugins/ and enable it via the Settings UI. The Traefik plugin is included:
plugins/
└── traefik/ ← included; enable in Settings
See docs/plugins/ for a full plugin development guide.
Documentation
| Document | Contents |
|---|---|
docs/architecture.md |
How the app works: startup sequence, routing, scheduler, DB pattern |
docs/core/configuration.md |
Bootstrap config, DB-backed settings, all setting keys and defaults |
docs/core/monitors.md |
Ping and DNS monitors: probe logic, metrics emitted, data models |
docs/core/alerting.md |
Alert rules, state machine, notification channels, template variables |
docs/core/ansible.md |
Playbook sources, run lifecycle, SSE streaming |
docs/plugins/overview.md |
Plugin system: how loading works, plugin.yaml schema, required exports |
docs/plugins/writing-a-plugin.md |
Step-by-step plugin development guide |
docs/plugins/traefik.md |
Traefik plugin: config, metrics, alert rule examples |
docs/reference/code-map.md |
Where every key function, model, and route lives in the codebase |