<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Prometheus on rHomelab</title><link>https://rhomelab.com/tags/prometheus/</link><description>Recent content in Prometheus on rHomelab</description><generator>Hugo</generator><language>en-US</language><lastBuildDate>Thu, 20 Aug 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://rhomelab.com/tags/prometheus/index.xml" rel="self" type="application/rss+xml"/><item><title>Self-Hosted Monitoring: Setting Up Uptime Kuma and Grafana So You Know Before Your Users Do</title><link>https://rhomelab.com/self-hosted/self-hosted-monitoring-stack/</link><pubDate>Thu, 20 Aug 2026 00:00:00 +0000</pubDate><guid>https://rhomelab.com/self-hosted/self-hosted-monitoring-stack/</guid><description>A practical guide to layering uptime checks and real metrics on top of your homelab with Uptime Kuma and Grafana, so an outage shows up as an alert instead of a complaint.</description><content:encoded><![CDATA[<p>The first time most people find out a self-hosted service is down is when someone tries to use it and it isn&rsquo;t there. That&rsquo;s the wrong order. If you&rsquo;re running more than two or three services at home - Immich, a media server, a reverse proxy, whatever - you&rsquo;ve already got enough surface area for something to quietly die on a Tuesday and stay dead until you notice by accident.</p>
<p>Monitoring fixes that, but &ldquo;monitoring&rdquo; in the homelab world actually means two different things that get conflated constantly: <strong>uptime checking</strong> (is this thing responding right now) and <strong>metrics collection</strong> (what is this thing actually doing over time - CPU, memory, disk, request rates). You don&rsquo;t need a full observability stack to get real value. You need one tool for each job, wired together sensibly, and an alert that reaches you somewhere you&rsquo;ll actually see it.</p>
<h2 id="the-two-tool-split-and-why-one-tool-doesnt-do-both-well">The two-tool split, and why one tool doesn&rsquo;t do both well</h2>
<p><strong>Uptime Kuma</strong> answers &ldquo;is it up.&rdquo; You give it a URL, a port, a ping target, or a Docker container name, and it checks on an interval - 60 seconds, 5 minutes, whatever you set. When a check fails, it fires a notification. That&rsquo;s the entire job, and it does it well: clean web UI, dozens of notification integrations built in, status pages if you want to show something public, and a Docker image that takes about five minutes to stand up.</p>
<p><strong>Grafana</strong>, paired with <strong>Prometheus</strong> as the data source, answers &ldquo;what&rsquo;s actually happening.&rdquo; Prometheus scrapes numeric metrics from your services and hosts on a regular interval and stores them as time series. Grafana turns that time series into dashboards - CPU trending up over three weeks, memory that never gets released after a restart, disk filling faster than it should. Uptime Kuma tells you a service died. Grafana tells you it was dying for two weeks before it did.</p>
<p>People try to make one tool do both jobs and end up disappointed either way. Uptime Kuma has a basic metrics/history view, but it&rsquo;s not built for real time-series analysis. Prometheus and Grafana can technically do uptime probing (via <code>blackbox_exporter</code>), but the setup is heavier than it needs to be for something as simple as &ldquo;ping this every minute.&rdquo; Run both, let each do what it&rsquo;s actually good at.</p>
<h2 id="setting-up-uptime-kuma">Setting up Uptime Kuma</h2>
<p>This is the easy half. A single-container Docker Compose stack:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">uptime-kuma</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">louislam/uptime-kuma:1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">uptime-kuma</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./data:/app/data</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;3001:3001&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span></code></pre></div><p>Bring it up, hit the web UI, create your admin account on first login, and start adding monitors. For each service you care about, add a monitor of the matching type:</p>
<ul>
<li><strong>HTTP(s)</strong> for anything with a web UI - point it at the actual login page, not just the root domain, so a &ldquo;page loads but auth is broken&rdquo; scenario still gets caught.</li>
<li><strong>TCP port</strong> for things without HTTP, like a database or an SSH daemon you want to confirm is listening.</li>
<li><strong>Docker container</strong> if Uptime Kuma has access to the Docker socket - it can check container health directly rather than poking at a port.</li>
<li><strong>Ping</strong> for raw host reachability, useful for things like your router or a NAS that doesn&rsquo;t run a web service you can hit.</li>
</ul>
<p>Set the check interval per monitor, not globally. A reverse proxy or auth service that everything else depends on deserves a 30-60 second interval. A low-stakes internal tool checked once every 5 minutes is fine and saves you from a monitor list that hammers your network for no reason.</p>
<p>The part that actually matters is notifications. Uptime Kuma supports dozens of targets out of the box - Discord, Telegram, ntfy, Pushover, generic webhooks, email. Pick something that reaches you outside your homelab network, because if your internet or your core router is what went down, a notification that only works over LAN never arrives. Discord or a phone-push service like ntfy/Pushover both clear that bar cheaply.</p>
<p>One setting worth changing from the default: <strong>retries before alert.</strong> Straight out of the box, Uptime Kuma can fire on the very first failed check, which turns a single dropped packet into a 3am notification. Set retries to 2-3 with a short interval between them before it actually alerts - real outages persist past one bad check, transient blips don&rsquo;t.</p>
<h2 id="setting-up-prometheus-and-grafana">Setting up Prometheus and Grafana</h2>
<p>This stack has more moving parts, but the shape is standard and doesn&rsquo;t change much between setups. Three pieces:</p>
<ol>
<li><strong>Prometheus</strong> - the time-series database and scraper.</li>
<li><strong>Exporters</strong> - small agents that expose metrics in a format Prometheus understands. <code>node_exporter</code> for host-level stats (CPU, memory, disk, network) runs on every machine you want visibility into. Most self-hosted apps that care about observability ship their own <code>/metrics</code> endpoint already (Lemmy and Immich both do, for example).</li>
<li><strong>Grafana</strong> - the dashboard layer that queries Prometheus and renders it.</li>
</ol>
<p>A minimal <code>docker-compose.yaml</code> for the Prometheus + Grafana half:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">prometheus</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">prom/prometheus:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./prometheus.yml:/etc/prometheus/prometheus.yml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./prometheus-data:/prometheus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;9090:9090&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">grafana</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">grafana/grafana:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./grafana-data:/var/lib/grafana</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;3000:3000&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span></code></pre></div><p>And a <code>prometheus.yml</code> that scrapes a node exporter and itself:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">global</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">scrape_interval</span><span class="p">:</span><span class="w"> </span><span class="l">15s</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">scrape_configs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">job_name</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;prometheus&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">static_configs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">targets</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s1">&#39;localhost:9090&#39;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">job_name</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;node&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">static_configs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">targets</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s1">&#39;10.0.50.10:9100&#39;</span><span class="p">]</span><span class="w">
</span></span></span></code></pre></div><p>If you&rsquo;re running any of this inside an LXC container rather than a full VM, the usual gotcha applies: Docker&rsquo;s default bridge networking can fight with LXC&rsquo;s confinement (permission errors on network-namespace sysctls). Setting <code>network_mode: host</code> on the containers sidesteps it cleanly and is the standard fix for Docker-in-LXC setups generally.</p>
<p>Once Prometheus is scraping, add it as a data source in Grafana (<code>http://prometheus:9090</code> if they&rsquo;re on the same Docker network, or the host IP and port otherwise) and import a pre-built dashboard rather than building one from scratch. The community dashboard library at grafana.com has solid ready-made options for <code>node_exporter</code> specifically - search for the node exporter full dashboard and import it by ID. You&rsquo;ll have CPU, memory, disk, and network graphs for every host in a couple of minutes instead of an afternoon.</p>
<h2 id="alerting-on-metrics-not-just-uptime">Alerting on metrics, not just uptime</h2>
<p>The real payoff of the Prometheus/Grafana half isn&rsquo;t the dashboards, it&rsquo;s catching trouble before it becomes an outage. Grafana&rsquo;s alerting can watch a Prometheus query and fire a notification when it crosses a threshold - disk usage over 85%, a host&rsquo;s load average sustained above its core count for 5+ minutes, memory usage climbing without ever coming back down after a restart (a classic slow-leak signature).</p>
<p>Start with two or three alerts that map to things that have actually bitten you before, rather than trying to cover everything on day one. A disk-almost-full alert and a &ldquo;this container&rsquo;s CPU has been pegged for 5 minutes straight&rdquo; alert cover a large share of real homelab incidents by themselves. Route these to the same notification channel as Uptime Kuma so there&rsquo;s one place you check, not two.</p>
<h2 id="what-this-buys-you">What this buys you</h2>
<p>None of this prevents outages. What it does is change the moment you find out about one from &ldquo;someone complains&rdquo; or &ldquo;you happen to open the app&rdquo; to &ldquo;a notification lands on your phone within a minute or two.&rdquo; For a single-operator homelab, that&rsquo;s most of the value observability tooling exists to provide - not root-cause analysis, not SLA dashboards, just the basic guarantee that silence means things are actually fine, not that nobody&rsquo;s looked lately.</p>
<p>Skip the temptation to monitor everything on day one. Wire up Uptime Kuma for the handful of services you&rsquo;d actually be upset to lose without knowing, add Prometheus and Grafana once that&rsquo;s stable, and expand from there as you find gaps - usually right after the first outage that gets past you undetected. That one will happen either way. The goal is making sure it only happens once.</p>
]]></content:encoded></item></channel></rss>