<?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>Grafana – Serverküche</title><link>https://serverkueche.de/en/tags/grafana/</link><description>Grafana – Neueste Beiträge von Serverküche</description><generator>Hugo</generator><language>en-US</language><managingEditor>feedback@serverkueche.de (Serverküche)</managingEditor><webMaster>feedback@serverkueche.de (Serverküche)</webMaster><copyright>2026 Serverküche</copyright><lastBuildDate>Tue, 21 Jul 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://serverkueche.de/en/tags/grafana/index.xml" rel="self" type="application/rss+xml"/><item><title>Monitoring with Grafana &amp; Prometheus: your server in live dashboards</title><link>https://serverkueche.de/en/tutorials/monitoring-grafana-prometheus/</link><pubDate>Tue, 21 Jul 2026 00:00:00 +0000</pubDate><author>feedback@serverkueche.de (Serverküche)</author><guid>https://serverkueche.de/en/tutorials/monitoring-grafana-prometheus/</guid><description>A complete monitoring stack of Prometheus, node-exporter, cAdvisor and Grafana behind Traefik – with live metrics for host and containers.</description><content:encoded><![CDATA[<p><a href="/en/tutorials/uptime-kuma-monitoring/">Uptime Kuma</a> tells you <em>whether</em> a service is
running. This stack tells you <em>how</em> it&rsquo;s doing: CPU, RAM, disk, network and the load of each
individual container – as live dashboards with history. We build the combination that has
become the de facto standard in self-hosting: Prometheus collects, Grafana shows.</p>
<h2 id="what-are-we-building">What are we building?</h2>
<p>By the end, a complete <strong>monitoring stack</strong> runs behind your Traefik proxy, reachable at
<code>https://grafana.YOUR_DOMAIN</code> with valid HTTPS. Four building blocks interlock:</p>
<ul>
<li><strong>Prometheus 3.13</strong> – the time-series database. It <em>pulls</em> (scrapes) metrics from the other
services at intervals and stores them with a timestamp.</li>
<li><strong>node-exporter 1.12</strong> – delivers the <strong>host metrics</strong>: CPU, memory, load, disks, network
interfaces.</li>
<li><strong>cAdvisor 0.55</strong> – delivers the <strong>container metrics</strong>: CPU and RAM per running container,
so you see which service is eating.</li>
<li><strong>Grafana 13.1</strong> – the interface: ready-made dashboards that turn the Prometheus data into
graphs and displays.</li>
</ul>
<p>The decisive difference from Uptime Kuma: instead of &ldquo;green/red&rdquo; you get <strong>trends</strong> – you
recognize that the RAM has been slowly filling up for days, <em>before</em> the server swaps. Only
Grafana is reachable from outside; Prometheus, node-exporter and cAdvisor get no public route.</p>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-herb-400 bg-herb-50 dark:border-herb-700 dark:bg-herb-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">🧑‍🍳</span>How big does the server need to be?
  </p>
  <div class="prose-kitchen text-sm">The stack itself is frugal – in the test it occupied only a few hundred MB of RAM. The
storage hunger comes from Prometheus&rsquo;s <strong>retention time</strong>: the longer you keep metrics and
the more services you monitor, the more space and RAM the time-series database needs. Because
monitoring usually runs <em>in addition</em> to your actual apps, we recommend the VPS 2000 with more
reserve – for a pure test setup the smaller one is enough too.</div>
</div>
<div class="not-prose my-6 overflow-hidden rounded-xl border border-paprika-200 bg-paprika-50 dark:border-paprika-800 dark:bg-paprika-900/20">
  <div class="flex items-center justify-between border-b border-paprika-200 bg-paprika-100 px-4 py-1.5 text-xs font-semibold uppercase tracking-wide text-paprika-700 dark:border-paprika-800 dark:bg-paprika-900/40 dark:text-paprika-300">
    <span>🍳 Recommendation</span>
    <span title="Links marked with * are affiliate links.">Ad</span>
  </div>
  <div class="flex flex-col gap-4 p-4 sm:flex-row sm:items-center sm:justify-between">
    <div>
      <p class="text-lg font-bold text-slate-900 dark:text-white">VPS 2000 G12</p>
      <p class="mt-1 text-sm text-slate-600 dark:text-slate-300">8 vCores · 16 GB RAM · 512 GB NVMe</p>
      <p class="mt-1 text-sm font-semibold text-paprika-700 dark:text-paprika-400">from €19.24/month</p>
      <p class="mt-2 text-sm text-slate-600 dark:text-slate-400">Enough RAM and storage to run monitoring alongside your apps.</p>
    </div>
    <a href="https://www.netcup.com/en/server/vps/vps-2000-g12-iv-12m?ref=44083" rel="sponsored noopener" target="_blank"
   class="inline-flex shrink-0 items-center justify-center rounded-lg bg-paprika-600 px-5 py-2.5 font-semibold text-white transition-colors hover:bg-paprika-700">
  Go to netcup →
</a>

  </div><div class="px-4 pb-4"><p class="not-prose my-3 flex flex-wrap items-center gap-x-2 gap-y-1 rounded-lg border border-herb-500/40 bg-herb-50 px-3 py-2 text-sm text-slate-700 dark:bg-herb-900/20 dark:text-slate-200">
  <span>💶 <strong>5 € voucher</strong> for new netcup customers:</span>
  <code class="rounded bg-white px-2 py-0.5 font-mono text-sm font-semibold text-herb-800 dark:bg-slate-800 dark:text-herb-400">36nc17844976032</code>
  <span class="text-xs text-slate-500 dark:text-slate-400">(new customers only, no domains)</span>
</p></div>
</div>

<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li>Confidence with <strong>Docker Compose</strong> – services, volumes, networks. If that&rsquo;s still new:
<a href="/en/tutorials/docker-compose-basics/">understanding Docker Compose</a>.</li>
<li>A running <strong>Traefik reverse proxy</strong> with the shared <code>proxy</code> network and the Let&rsquo;s Encrypt
resolver <code>le</code> – set up as in <a href="/en/tutorials/traefik-reverse-proxy/">reverse proxy with Traefik</a>.</li>
<li>A subdomain <code>grafana.YOUR_DOMAIN</code> whose DNS record (A/AAAA) points to your server IP – see
<a href="/en/tutorials/connect-domain-to-server/">connecting a domain to your server</a>.</li>
</ul>
<h2 id="step-by-step">Step by step</h2>
<h3 id="step-1-understand-the-architecture">Step 1: Understand the architecture</h3>
<p>Before we create files, the picture behind it – otherwise you debug blind. Prometheus works
on the <strong>pull principle</strong>: the services don&rsquo;t send their values somewhere, but Prometheus
queries them actively. Every &ldquo;exporter&rdquo; provides its metrics under <code>/metrics</code>, Prometheus
fetches them every 15 seconds and stores them in its time-series database. Grafana in turn
queries Prometheus and draws the graphs from it.</p>
<p>Why pull instead of push at all? Because this way Prometheus always knows whether a target is
<em>alive</em>: if an exporter doesn&rsquo;t respond, that&rsquo;s itself information (<code>up = 0</code>). You need no
agents that actively &ldquo;phone home&rdquo;, and you can simply enter every new target into the
configuration. An exporter is nothing magical – it&rsquo;s a tiny web server that delivers a text
list of current measurements under <code>/metrics</code>.</p>
<p>From this follows the security rule of this setup: node-exporter, cAdvisor and Prometheus have
<strong>no authentication</strong>. So they must <strong>never</strong> stand open on the internet. Prometheus and
cAdvisor we bind only to the internal Docker network <code>monitoring</code>; node-exporter runs in the
<strong>host network</strong> (why is explained in step 4), but stays unreachable from outside thanks to
<a href="/en/tutorials/firewall-ufw-setup/">UFW</a> and the
<a href="/en/tutorials/netcup-firewall-setup/">netcup firewall</a>. Only Grafana is additionally on the
<code>proxy</code> network and gets a Traefik route – the only service with a login and a public address.</p>
<h3 id="step-2-create-the-project-folder-and-configure-prometheus">Step 2: Create the project folder and configure Prometheus</h3>
<p>Create the folder structure:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">mkdir -p ~/monitoring/prometheus ~/monitoring/grafana/provisioning/datasources
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> ~/monitoring</span></span></code></pre></div>
</div>
<p>Create the Prometheus configuration. It defines <em>whom</em> Prometheus queries at what interval:</p>
<div class="sk-code">
  <span class="sk-code-head">YAML</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="c"># prometheus/prometheus.yml</span><span class="w">
</span></span></span><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 class="nt">scrape_timeout</span><span class="p">:</span><span class="w"> </span><span class="l">10s</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="l">prometheus</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="s2">&#34;localhost:9090&#34;</span><span class="p">]</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">job_name</span><span class="p">:</span><span class="w"> </span><span class="l">node</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="s2">&#34;host.docker.internal:9100&#34;</span><span class="p">]</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">job_name</span><span class="p">:</span><span class="w"> </span><span class="l">cadvisor</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="s2">&#34;cadvisor:8080&#34;</span><span class="p">]</span></span></span></code></pre></div>
</div>
<p>Important: <code>cadvisor:8080</code> appears as a <strong>service name</strong> – Docker resolves it automatically in
the shared <code>monitoring</code> network. The node-exporter, on the other hand, runs in the <strong>host
network</strong> (step 4 explains why) and is therefore addressed via <code>host.docker.internal:9100</code> –
Prometheus gets this name mapped to the Docker host&rsquo;s IP via <code>extra_hosts</code> shortly.
<code>scrape_interval: 15s</code> is a good compromise – often enough for meaningful curves, seldom
enough to hardly create load.</p>
<h3 id="step-3-provision-the-grafana-data-source-automatically">Step 3: Provision the Grafana data source automatically</h3>
<p>Instead of clicking the Prometheus data source into Grafana by hand later, we set it up via
<strong>provisioning</strong> – as a file. That&rsquo;s reproducible: after a rebuild, everything is immediately
back, without clicking.</p>
<div class="sk-code">
  <span class="sk-code-head">YAML</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="c"># grafana/provisioning/datasources/datasource.yml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="m">1</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">datasources</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Prometheus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">prometheus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">access</span><span class="p">:</span><span class="w"> </span><span class="l">proxy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">url</span><span class="p">:</span><span class="w"> </span><span class="l">http://prometheus:9090</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">isDefault</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">editable</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span></span></span></code></pre></div>
</div>
<p><code>access: proxy</code> means that <strong>Grafana itself</strong> (server-side) queries Prometheus – the user&rsquo;s
browser never talks to Prometheus directly. That&rsquo;s exactly why Prometheus can stay internal.</p>
<h3 id="step-4-the-compose-stack">Step 4: The Compose stack</h3>
<p>Now the heart. Create the <code>compose.yaml</code>. Replace <code>grafana.YOUR_DOMAIN</code> and the Grafana
password:</p>
<div class="sk-code">
  <span class="sk-code-head">YAML</span>
  <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:v3.13.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">prometheus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">command</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;--config.file=/etc/prometheus/prometheus.yml&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;--storage.tsdb.path=/prometheus&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;--storage.tsdb.retention.time=30d&#34;</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/prometheus.yml:/etc/prometheus/prometheus.yml:ro</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">prom_data:/prometheus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">extra_hosts</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;host.docker.internal:host-gateway&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">networks</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">monitoring]</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">node-exporter</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/node-exporter:v1.12.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">node-exporter</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">command</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;--path.rootfs=/host&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">network_mode</span><span class="p">:</span><span class="w"> </span><span class="l">host</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">pid</span><span class="p">:</span><span class="w"> </span><span class="l">host</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="s2">&#34;/:/host:ro,rslave&#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">cadvisor</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">gcr.io/cadvisor/cadvisor:v0.55.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">cadvisor</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">privileged</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">devices</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/dev/kmsg</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">/:/rootfs:ro</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/var/run:/var/run:ro</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/sys:/sys:ro</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/var/lib/docker/:/var/lib/docker:ro</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/dev/disk/:/dev/disk:ro</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">networks</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">monitoring]</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:13.1.0</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">grafana</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">prometheus]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">GF_SECURITY_ADMIN_USER</span><span class="p">:</span><span class="w"> </span><span class="l">admin</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">GF_SECURITY_ADMIN_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="l">YOUR_GRAFANA_PASSWORD</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">GF_SERVER_ROOT_URL</span><span class="p">:</span><span class="w"> </span><span class="l">https://grafana.YOUR_DOMAIN</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">GF_USERS_ALLOW_SIGN_UP</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;false&#34;</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="l">./grafana/provisioning:/etc/grafana/provisioning:ro</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">labels</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;traefik.enable=true&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.http.routers.grafana.rule=Host(`grafana.YOUR_DOMAIN`)&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.http.routers.grafana.entrypoints=websecure&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.http.routers.grafana.tls.certresolver=le&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.http.services.grafana.loadbalancer.server.port=3000&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">networks</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">monitoring, proxy]</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="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="nt">prom_data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">grafana_data</span><span class="p">:</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">networks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">monitoring</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">proxy</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">external</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span></span></span></code></pre></div>
</div>
<p>What matters here:</p>
<ul>
<li><strong><code>--storage.tsdb.retention.time=30d</code></strong> keeps metrics for 30 days. More = more meaning, but
also more storage. 30 days is a good start.</li>
<li><strong>node-exporter</strong> needs the <strong>host root directory</strong> (<code>/:/host:ro,rslave</code>), <code>pid: host</code>
<strong>and <code>network_mode: host</code></strong> to read real host values instead of container values.
<code>--path.rootfs=/host</code> tells it where the host view is (read-only, <code>:ro</code>). The host network
isn&rsquo;t a nice-to-have: <code>/proc/net</code> is bound to the network namespace – in a normal bridge
network, node-exporter would see <strong>only its own container interface</strong> (effectively just the
scrape traffic), not the real host interfaces. The price: node-exporter thus listens on port
<code>9100</code> of the host – your <a href="/en/tutorials/firewall-ufw-setup/">firewall</a> keeps the port closed
from outside, and Prometheus reaches it via the <code>extra_hosts</code> entry (<code>host-gateway</code>).</li>
<li><strong>cAdvisor</strong> needs <code>privileged: true</code>, the device <code>/dev/kmsg</code> and read mounts on <code>/sys</code> and
<code>/var/lib/docker</code> to detect all containers. That&rsquo;s unavoidable for container monitoring –
that&rsquo;s why cAdvisor stays strictly internal.</li>
<li><strong>Only <code>grafana</code></strong> carries Traefik labels and is on the <code>proxy</code> network. The internal port is
<strong>3000</strong>. <code>GF_SERVER_ROOT_URL</code> must be the public HTTPS address, otherwise logins and
redirects break behind the proxy.</li>
<li><strong><code>GF_USERS_ALLOW_SIGN_UP: &quot;false&quot;</code></strong> prevents strangers from creating their own account. Set
a <strong>strong admin password</strong> – Grafana is publicly accessible.</li>
</ul>
<h3 id="step-5-start-and-check-the-prometheus-targets">Step 5: Start and check the Prometheus targets</h3>
<p>Start the complete stack:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker compose up -d</span></span></code></pre></div>
</div>
<p>The first time, Docker downloads four images – that takes a moment. Check that all containers
are running:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker compose ps</span></span></code></pre></div>
</div>
<p>All four services (node-exporter, cadvisor, prometheus, grafana) should be <code>running</code>. Now the
decisive test: <strong>Does Prometheus see its targets?</strong> Since Prometheus stays internal, we query
it from a short-lived container that&rsquo;s on the same network (Compose calls the network
<code>monitoring_monitoring</code> – project folder plus network name):</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker run --rm --network monitoring_monitoring curlimages/curl:8.21.0 <span class="se">\
</span></span></span><span class="line"><span class="cl">  -s http://prometheus:9090/api/v1/query?query<span class="o">=</span>up</span></span></code></pre></div>
</div>
<p>In the JSON response there&rsquo;s a <code>&quot;value&quot;</code> with <code>&quot;1&quot;</code> for each target – that means &ldquo;reachable&rdquo;.
In the test all three jobs (<code>prometheus</code>, <code>node</code>, <code>cadvisor</code>) returned a <code>1</code>. A <code>0</code> means
Prometheus can&rsquo;t scrape the target – then the section &ldquo;When things go wrong&rdquo; helps.</p>
<h3 id="step-6-open-grafana-and-import-dashboards">Step 6: Open Grafana and import dashboards</h3>
<p>Open <code>https://grafana.YOUR_DOMAIN</code>. Traefik fetches the certificate on the first access (a few
seconds). Log in with <code>admin</code> and your password from the <code>compose.yaml</code>. Thanks to
provisioning, the Prometheus data source is already connected – you find it under
<strong>Connections → Data sources</strong>:</p>
<p><figure class="my-6"><img src="/en/tutorials/monitoring-grafana-prometheus/grafana-datasource_hu_399dc2e7f48b1788.webp" srcset="/en/tutorials/monitoring-grafana-prometheus/grafana-datasource_hu_7e0a1a479133fa45.webp 480w, /en/tutorials/monitoring-grafana-prometheus/grafana-datasource_hu_399dc2e7f48b1788.webp 768w, /en/tutorials/monitoring-grafana-prometheus/grafana-datasource_hu_c28443e38bd35aa1.webp 1200w, /en/tutorials/monitoring-grafana-prometheus/grafana-datasource_hu_b685c9265d189cbd.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/en/tutorials/monitoring-grafana-prometheus/grafana-datasource_hu_2947ead1a73e98f1.webp"
    alt="The provisioned Prometheus data source in Grafana with the internal URL http://prometheus:9090" title="The data source is provisioned via file – Grafana reports it as preconfigured"
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">The data source is provisioned via file – Grafana reports it as preconfigured</figcaption></figure></p>
<p>Now the dashboards. Instead of building panels ourselves, we import two proven ones from the
Grafana community. Go to <strong>Dashboards → New → Import</strong>, enter the ID and click <strong>Load</strong>:</p>
<p><figure class="my-6"><img src="/en/tutorials/monitoring-grafana-prometheus/grafana-import_hu_daeb0cdee97f29d5.webp" srcset="/en/tutorials/monitoring-grafana-prometheus/grafana-import_hu_ebf84a9b89d945c1.webp 480w, /en/tutorials/monitoring-grafana-prometheus/grafana-import_hu_daeb0cdee97f29d5.webp 768w, /en/tutorials/monitoring-grafana-prometheus/grafana-import_hu_b8420823a49d711c.webp 1200w, /en/tutorials/monitoring-grafana-prometheus/grafana-import_hu_26166f8d6195fe6c.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/en/tutorials/monitoring-grafana-prometheus/grafana-import_hu_c29ed6a2a42cab3a.webp"
    alt="The Grafana import dialog loads the &ldquo;Node Exporter Full&rdquo; dashboard from Grafana.com" title="Import a dashboard by ID – here \&#34;Node Exporter Full\&#34; (ID 1860)"
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">Import a dashboard by ID – here \&#34;Node Exporter Full\&#34; (ID 1860)</figcaption></figure></p>
<p>Import these two:</p>
<ul>
<li><strong>1860</strong> – &ldquo;Node Exporter Full&rdquo;: the comprehensive host dashboard.</li>
<li><strong>19792</strong> – &ldquo;cAdvisor Dashboard&rdquo;: metrics per container.</li>
</ul>
<p>On import, select the <strong>Prometheus</strong> data source each time. After that, &ldquo;Node Exporter Full&rdquo;
shows your host in real time – CPU usage, used RAM, disk, network, uptime:</p>
<p><figure class="my-6"><img src="/en/tutorials/monitoring-grafana-prometheus/grafana-node-dashboard_hu_caaac511989edd0f.webp" srcset="/en/tutorials/monitoring-grafana-prometheus/grafana-node-dashboard_hu_96720c854316db6f.webp 480w, /en/tutorials/monitoring-grafana-prometheus/grafana-node-dashboard_hu_caaac511989edd0f.webp 768w, /en/tutorials/monitoring-grafana-prometheus/grafana-node-dashboard_hu_b90af51a41705c01.webp 1200w, /en/tutorials/monitoring-grafana-prometheus/grafana-node-dashboard_hu_3e6bb19e3a185f34.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/en/tutorials/monitoring-grafana-prometheus/grafana-node-dashboard_hu_d1052dba15ec2b7e.webp"
    alt="The Grafana dashboard &ldquo;Node Exporter Full&rdquo; shows live values of the server: CPU 1 percent, RAM 14.7 percent used, 4 CPU cores, 8 GB RAM, uptime 2 days" title="Node Exporter Full with real live data from the test server"
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">Node Exporter Full with real live data from the test server</figcaption></figure></p>
<p>The cAdvisor dashboard breaks the same load down to <strong>individual containers</strong> – here you
immediately see which service draws how much CPU and RAM:</p>
<p><figure class="my-6"><img src="/en/tutorials/monitoring-grafana-prometheus/grafana-container-dashboard_hu_69ee0d4d1a780625.webp" srcset="/en/tutorials/monitoring-grafana-prometheus/grafana-container-dashboard_hu_9f644b6b9b88fc4f.webp 480w, /en/tutorials/monitoring-grafana-prometheus/grafana-container-dashboard_hu_69ee0d4d1a780625.webp 768w, /en/tutorials/monitoring-grafana-prometheus/grafana-container-dashboard_hu_db3e2021d7ef215.webp 1200w, /en/tutorials/monitoring-grafana-prometheus/grafana-container-dashboard_hu_41bcb30176acfef2.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/en/tutorials/monitoring-grafana-prometheus/grafana-container-dashboard_hu_412631eaba466c56.webp"
    alt="The cAdvisor dashboard shows CPU consumption per container – each color is a container like grafana, prometheus or traefik" title="cAdvisor: CPU and memory consumption per container, broken down by name"
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">cAdvisor: CPU and memory consumption per container, broken down by name</figcaption></figure></p>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-herb-400 bg-herb-50 dark:border-herb-700 dark:bg-herb-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">🧑‍🍳</span>Provision dashboards too
  </p>
  <div class="prose-kitchen text-sm">Like the data source, dashboards can also be provisioned via file (under
<code>grafana/provisioning/dashboards/</code> a <code>.yml</code> plus the dashboard JSONs). For getting started, the
import via ID is faster; once your setup is in place, provisioning is worth it so all dashboards
are automatically back after a rebuild.</div>
</div>
<h3 id="step-7-read-the-most-important-metrics-correctly">Step 7: Read the most important metrics correctly</h3>
<p>A dashboard full of curves is of little use if you don&rsquo;t know what to watch for. These five
values in the &ldquo;Node Exporter Full&rdquo; dashboard tell you almost everything about the health of
your server:</p>
<ul>
<li><strong>CPU Busy.</strong> Short spikes are normal. It gets critical when the usage stays <em>permanently</em>
high – then the server is the bottleneck.</li>
<li><strong>Sys Load.</strong> The load should on average be below the number of your CPU cores (in the test:
4 cores → load permanently above 4 is a warning sign of overload).</li>
<li><strong>RAM Used.</strong> Important: Linux uses free memory as a file cache – so &ldquo;used&rdquo; isn&rsquo;t the same as
&ldquo;tight&rdquo;. Only when little stays free <em>besides</em> the cache and <strong>SWAP Used</strong> rises does the
server get sluggish. It&rsquo;s exactly this creeping rise you want to see early.</li>
<li><strong>Root FS Used.</strong> The classic among server outages is the full disk. The trend shows you the
fill rate – so you recognize days in advance when it&rsquo;s getting tight.</li>
<li><strong>Network Traffic.</strong> Unusual spikes even though nothing is running can indicate a backup, an
update or – in the bad case – unwanted traffic.</li>
</ul>
<p>In the cAdvisor dashboard the most exciting question is: <strong>Which container draws the load?</strong> If
your server suddenly gets slow, you see here at a glance whether it&rsquo;s due to a single app – and
don&rsquo;t have to guess. This turns &ldquo;something is slow&rdquo; into a targeted &ldquo;the photo import in
container X is eating the CPU right now&rdquo;.</p>
<h2 id="when-things-go-wrong">When things go wrong</h2>
<div class="troubleshoot not-prose">
<p><strong>A target is at <code>up = 0</code> or &ldquo;DOWN&rdquo; in Prometheus.</strong> Prometheus can&rsquo;t reach the exporter. For
<code>cadvisor</code>, check that it&rsquo;s on the same <code>monitoring</code> network and the name and port are exactly
right (<code>cadvisor:8080</code>). For the <code>node</code> job, check the <code>extra_hosts</code> entry on the Prometheus
service and the target <code>host.docker.internal:9100</code> – without both, Prometheus doesn&rsquo;t find the
node-exporter in the host network. After changes to the <code>prometheus.yml</code>, restart the
Prometheus container: <code>docker compose restart prometheus</code>.</p>
<p><strong>The cAdvisor dashboard partly shows &ldquo;No data&rdquo; or cAdvisor won&rsquo;t start.</strong> If the mounts or
<code>privileged: true</code> are missing, cAdvisor doesn&rsquo;t see the containers. Check the <code>cadvisor</code> block
(mounts, <code>devices: /dev/kmsg</code>). Individual &ldquo;No data&rdquo; panels are normal – some metrics (like CPU
throttling) only exist if you&rsquo;ve set CPU limits on the containers.</p>
<p><strong>Grafana doesn&rsquo;t load correctly behind Traefik – login fails or the layout is broken.</strong> Almost
always <code>GF_SERVER_ROOT_URL</code> is wrong. It must be exactly the public address
(<code>https://grafana.YOUR_DOMAIN</code>). Also check that the Traefik label
<code>loadbalancer.server.port=3000</code> is set – Grafana listens on 3000 internally.</p>
<p><strong>Grafana shows &ldquo;No data&rdquo; in the panels, even though the targets are <code>up</code>.</strong> Usually the time
range. A fresh stack has no history yet – set &ldquo;Last 15 minutes&rdquo; at the top right and wait a few
minutes for data to accumulate. Also check that the <strong>Prometheus</strong> data source was selected on
dashboard import.</p>
<p><strong>The disk space on the server grows steadily.</strong> That&rsquo;s the Prometheus TSDB. Reduce
<code>--storage.tsdb.retention.time</code> (e.g. to <code>15d</code>) or monitor fewer targets. The <code>prom_data</code>
volume grows with the number of metrics × retention time.</p>

</div>

<h2 id="maintenance--backups">Maintenance &amp; backups</h2>
<ul>
<li><strong>What you must back up.</strong> Important is the <strong><code>grafana_data</code> volume</strong>: it contains your users,
settings and self-built dashboards. Back it up encrypted and off-site with
<a href="/en/tutorials/restic-backups/">Restic</a>. The Prometheus metrics (<code>prom_data</code>) are pure
measurements – a loss is bearable, a backup optional.</li>
<li><strong>Updates.</strong> The image tags are pinned (<code>prometheus:v3.13.1</code>, <code>grafana:13.1.0</code> …). To update,
raise the tags, then <code>docker compose pull &amp;&amp; docker compose up -d</code>. Read the release notes
first – Grafana in particular occasionally changes defaults between major versions. You get
cAdvisor from <code>gcr.io</code>; there <code>v0.55.1</code> is currently the latest available image version.</li>
<li><strong>Security.</strong> Grafana is publicly accessible – assign a strong admin password and leave
<code>GF_USERS_ALLOW_SIGN_UP</code> at <code>false</code>. Prometheus and cAdvisor stay internal; don&rsquo;t expose them
&ldquo;just quickly&rdquo; for debugging, they have no authentication.</li>
<li><strong>The next step: alerts.</strong> A dashboard only helps if you look at it. Grafana can trigger
alerts itself at thresholds (RAM &gt; 90%, disk almost full) and report e.g. via push. This turns
the pretty dashboard into an early-warning system – a good connection to your existing
<a href="/en/tutorials/uptime-kuma-monitoring/">Uptime Kuma</a>.</li>
<li><strong>Honest about the effort:</strong> the stack runs largely on its own after setup. Occasionally plan
a look at the retention time and storage usage, and bump the versions roughly quarterly.</li>
</ul>
]]></content:encoded></item></channel></rss>