Mark Christian Klass
Case 01Public sourceSample data

pvewatch

A single container that tells you when Proxmox backups fail, VMs go down or storage fills up. No hook scripts, no Prometheus required, no writes to the cluster.

Status
Released, in use on my cluster
Latest release
v0.2.0 · 2026-07-22 checked 2026-11-04
Stack
Python 3.12 stdlib · SQLite or PostgreSQL · Jinja2 · Docker
Licence
MIT
AI in this project
None at runtime. Built with AI-assisted development; design decisions and review are mine.

Problem

Proxmox records every backup as a task, and a failed task is one line in a log you have to go looking for. The common "back up all VMs" job is worse: it is a single task with no VM attached, so a failure inside it has no owner and no history. Grafana-based setups answer this with exporters, dashboards and alert rules that take longer to set up than the backup schedule itself.

What I built

pvewatch polls the Proxmox API with a read-only auditor token every 15 minutes, keeps per-VM backup history in SQLite by default (PostgreSQL is optional), and alerts by email or Discord with the last 20 lines of the failing task log. A read-only dashboard shows a per-VM heatmap over 3 to 30 days, storage pool usage deduplicated across nodes, and a Sunday digest. The same data feeds a JSON API and a Prometheus endpoint, so it slots into an existing stack when there is one.

If the monitor itself dies it cannot tell you, so it pings a heartbeat URL after every successful poll. The external service alerts when the pings stop, which also catches "container up, Proxmox unreachable".

How it works

One data builder runs the queries once and feeds all three outputs, so a change to how failures are counted shows up everywhere at once.

The decision

Parse the batch log instead of changing the user's backup job. A "back up all VMs" task has no VM ID. pvewatch fetches the full task log, parses the per-VM start and finish lines, and stores each result under a synthetic ID made from the batch task and the VM. The batch ID is remembered so the log is never fetched twice. Users keep the backup mode they already have, and the heatmap still fills in per VM.

Also decided, and written down in the repo: SQLite as the zero-setup default, the standard-library HTTP server over a framework, an inline template with no frontend build, and no API versioning for a self-hosted single-user tool. Each has a dated entry in DECISIONS.md with the reasoning.

Evidence

The dashboard as shipped, from the repository's README. Node and VM names in it are synthetic.
  • CI
    ruff, pytest with coverage, a SonarQube quality gate on merge requests and main; a public GitHub Actions pipeline lints, tests and builds the image.
  • Distribution
    ghcr.io image for amd64 and arm64, semver tags from git tags, Kubernetes manifests included.
  • In production
    Watches my own Proxmox host; the heartbeat goes to an external check that alerts when the pings stop.
Try it

The dashboard on sample data, in your browser

Nothing is fetched. Inject a failure and watch the alert that would be sent.
Open the demo