Uptime Kuma is the small monitor that watches the rest of the homelab—and indirectly tells this site when the lab was last known to be alive.
The monitoring path has two layers. Uptime Kuma performs the checks and keeps their history. A separate heartbeat job reads a deliberately limited view and pushes a static status snapshot to the public site.
homelab services
│
▼
Uptime Kuma
│
▼
heartbeat job every 15 minutes
│
▼
static public status JSON
│
▼
mutana.xyz status page
The public page does not query the private monitor from a visitor’s browser. It serves the last pushed snapshot and shows when that snapshot has become stale.
That is a small architecture with a useful failure mode.
Why push the status?
Directly exposing the monitor’s API would couple the public site to the private cluster and reveal more than the site needs.
The pushed heartbeat creates a narrow contract:
service name
category
coarse state
uptime summary
generation timestamp
The public site can render that data without credentials or a live connection back into the homelab.
If the cluster, the monitor or the heartbeat job fails, the last file remains available. The timestamp eventually becomes stale and the site says so. Frozen green data is not repainted as current truth.
Current deployment
Uptime Kuma runs as a single StatefulSet in the monitoring namespace:
Traefik
│
▼
Service on port 3001
│
▼
Uptime Kuma 1.18.5
│
▼
1 GiB persistent claim
The homelab volume uses Longhorn storage and mounts at /app/data. A development variant uses a different storage class but keeps the same workload shape.
The StatefulSet has one replica. This means the monitor has stable storage, not high availability. For a homelab monitor, a reliable history backup and an honest stale state are more valuable than pretending one in-cluster replica can observe every cluster-wide failure.
The observer problem
A monitor inside the platform it watches has a blind spot. If the whole cluster disappears, Uptime Kuma disappears with it.
The pushed status file softens that problem but does not eliminate it:
cluster healthy
→ current heartbeat arrives
cluster unhealthy
→ heartbeat stops
→ public site keeps last snapshot
→ snapshot ages into stale state
This is not equivalent to an independent external probe. It is an honest description of what the current architecture knows.
For services where independent outside-in availability matters, an external check would be the next layer. The current public status answers a narrower question: when did the homelab last report its own state?
Monitoring configuration is state
The /app/data volume contains more than a decorative history graph. It holds monitor definitions, notification relationships and application state.
If that volume is replaced with an empty claim, Kubernetes can show a healthy Uptime Kuma pod with zero useful monitors.
process health
└── is Uptime Kuma running?
monitoring health
└── are the expected checks running and producing recent results?
Both questions matter after a restore.
Operating Uptime Kuma
I start with the StatefulSet, route and volume:
kubectl -n monitoring get statefulset,pod,svc,pvc,ingressroute
kubectl -n monitoring logs statefulset/uptime-kuma --tail=200
kubectl -n monitoring describe statefulset uptime-kuma
Then I inspect the public heartbeat separately. If Uptime Kuma is healthy but the public timestamp is old, the failure is likely in the export or upload path rather than in monitoring itself.
The useful split is:
pod Pending
└── Longhorn claim or scheduling
pod Ready, UI unavailable
└── Service, Traefik or access control
UI healthy, monitors missing
└── wrong or empty data volume
monitors current, public status stale
└── heartbeat job, credential or upload path
many monitors fail together
└── shared DNS, ingress, network or cluster dependency
A burst of simultaneous red checks often says more about the monitoring vantage point than about every application failing independently.
End-to-end validation
After a monitoring change, I validate the whole publication chain:
representative service responds
→ Uptime Kuma records the new check
→ heartbeat job exports a fresh snapshot
→ public timestamp advances
→ public state matches the private coarse state
I also test staleness in the site fixture. A status page that only looks correct while data is fresh has not implemented its most important failure state.
Backup and recovery
The useful recovery set is:
Uptime Kuma data volume
+
heartbeat export configuration
+
protected upload credentials
+
Kubernetes manifests
+
public stale-state behaviour
My recovery sequence is:
1. Restore the 1 GiB application data
2. Reconcile the StatefulSet, Service and route
3. Confirm the expected monitors and recent history
4. Restore the heartbeat job inputs
5. run a dry export
6. publish one fresh heartbeat
7. verify the public timestamp and service rows
The public status snapshot is replaceable. The monitor catalogue and its history are the state worth preserving.
Versioning
The deployment currently pins Uptime Kuma 1.18.5. That makes rollbacks reproducible, but the version also deserves a planned upgrade path rather than permanent stability by neglect.
Before changing it I snapshot /app/data, read the intervening migration notes, update one environment first and confirm both the UI and heartbeat contract. The public JSON should not have to change merely because the monitor’s internal schema changes.
Keeping that contract small is what decouples the site from the monitoring product.
Things worth remembering
At the time of writing:
Uptime Kuma 1.18.5
Workload one StatefulSet
Namespace monitoring
Storage 1 GiB Longhorn claim
Ingress Traefik
Public status pushed static snapshot every 15 minutes
Failure mode explicit stale state
Recovery test monitor → heartbeat → public page
Monitoring is not only the act of detecting failure. It is also the discipline of saying what the monitoring system cannot currently know.