Quick answer

Node Exporter on each Proxmox node publishes CPU, RAM, disk, and network metrics at port 9100, Prometheus scrapes those targets every 15 seconds and writes them to its local TSDB at 15-day retention, and Grafana on port 3000 reads that Prometheus at http://prometheus:9090 to draw dashboard 1860. Put the TSDB on fast local storage; Prometheus writes continuously.

By LK Wood IV · 2026-06-05 · ~14 min read · St. Louis County, MO

Architecture diagram of the homelab monitoring stack: Node Exporter agents on the Docker host and Proxmox nodes pve01-pve03 (192.168.1.10-12, port 9100) plus a pve-exporter (port 9221) are scraped every 15 seconds by Prometheus (port 9090, 15-day retention), which feeds Grafana dashboards (port 3000, dashboards 1860 and 10347) and fires alert rules to Alertmanager (port 9093) that routes to Discord.

Uptime Kuma tells you when a service is down. Grafana + Prometheus tells you why it went down, what the CPU was doing three hours before it crashed, which disk is filling up, and which VM is hammering the network. They solve different problems. This guide sets up the full observability stack (new to the two tools and unsure what each does? Start with Grafana vs Prometheus explained).

What each component does

Prometheus is a time-series database and scraping engine. You define scrape targets (exporters running on each machine), and Prometheus polls them on the interval you configure (15 seconds in the config below), storing the metrics. It handles the data collection and storage.

Grafana is the dashboard layer. It connects to Prometheus as a data source, and you build (or import) dashboards that visualize the metrics as graphs, gauges, and tables.

Node Exporter is a Prometheus exporter that runs on Linux machines and exposes system metrics: CPU per-core, RAM, disk IO, filesystem usage, and network throughput. Run one process on every machine you want to monitor.

Alertmanager handles alert routing. Prometheus evaluates alert rules, fires alerts to Alertmanager, and Alertmanager sends them to your preferred notification channel (Slack, Discord, PagerDuty, email, Telegram).

Stack setup with Docker Compose

All components run in Docker containers on your existing Docker host. Create the monitoring stack directory:

mkdir -p /opt/stacks/monitoring/{prometheus,grafana,alertmanager}
cd /opt/stacks/monitoring

Prometheus configuration:

# /opt/stacks/monitoring/prometheus/prometheus.yml
global:
  scrape_interval: 15s
  evaluation_interval: 15s

rule_files:
  - "alerts.yml"

alerting:
  alertmanagers:
    - static_configs:
        - targets: ["alertmanager:9093"]

scrape_configs:
  # Monitor the Prometheus instance itself
  - job_name: "prometheus"
    static_configs:
      - targets: ["localhost:9090"]

  # Monitor the Docker host
  - job_name: "node-docker-host"
    static_configs:
      - targets: ["node-exporter:9100"]
    relabel_configs:
      - target_label: instance
        replacement: "docker-host"

  # Remote Proxmox nodes — install node_exporter on each
  - job_name: "proxmox-nodes"
    static_configs:
      - targets:
          - "192.168.1.10:9100"    # pve01
          - "192.168.1.11:9100"    # pve02
          - "192.168.1.12:9100"    # pve03
    relabel_configs:
      - source_labels: [__address__]
        target_label: instance

Alert rules:

# /opt/stacks/monitoring/prometheus/alerts.yml
groups:
  - name: homelab
    rules:
      - alert: NodeDown
        expr: up == 0
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "Node {{ $labels.instance }} is down"

      - alert: HighCPU
        expr: 100 - (avg by(instance)(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 85
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "CPU usage over 85% on {{ $labels.instance }}"

      - alert: DiskAlmostFull
        expr: (node_filesystem_avail_bytes{fstype!~"tmpfs|fuse.lxcfs"} / node_filesystem_size_bytes) * 100 < 15
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Disk {{ $labels.mountpoint }} on {{ $labels.instance }} has {{ $value | printf \"%.0f\" }}% free"

      - alert: HighRAMUsage
        expr: (1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100 > 90
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "RAM usage over 90% on {{ $labels.instance }}"

Alertmanager configuration:

# /opt/stacks/monitoring/alertmanager/alertmanager.yml
global:
  resolve_timeout: 5m

route:
  group_by: ['alertname', 'instance']
  group_wait: 10s
  group_interval: 10m
  repeat_interval: 12h
  receiver: 'discord'

receivers:
  - name: 'discord'
    discord_configs:
      - webhook_url: 'https://discord.com/api/webhooks/YOUR-WEBHOOK-URL'
        title: '{{ .GroupLabels.alertname }}'
        message: '{{ range .Alerts }}{{ .Annotations.summary }}{{ end }}'

Replace the Discord webhook URL with your own. Alertmanager also supports Slack, PagerDuty, Telegram, email, and many others — see the Alertmanager docs for other receivers.

Docker Compose file:

# /opt/stacks/monitoring/docker-compose.yml
services:
  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    ports:
      - "9090:9090"
    restart: unless-stopped
    volumes:
      - ./prometheus:/etc/prometheus
      - prometheus_data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
      - '--storage.tsdb.retention.time=15d'
      - '--web.enable-lifecycle'
    networks:
      - monitoring
      - proxy

  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    ports:
      - "3000:3000"
    restart: unless-stopped
    volumes:
      - grafana_data:/var/lib/grafana
    environment:
      GF_SECURITY_ADMIN_PASSWORD: "change-this-password"
      GF_USERS_ALLOW_SIGN_UP: "false"
    networks:
      - monitoring
      - proxy

  alertmanager:
    image: prom/alertmanager:latest
    container_name: alertmanager
    restart: unless-stopped
    volumes:
      - ./alertmanager:/etc/alertmanager
    networks:
      - monitoring

  node-exporter:
    image: prom/node-exporter:latest
    container_name: node-exporter
    restart: unless-stopped
    pid: host
    volumes:
      - /proc:/host/proc:ro
      - /sys:/host/sys:ro
      - /:/rootfs:ro
    command:
      - '--path.procfs=/host/proc'
      - '--path.sysfs=/host/sys'
      - '--path.rootfs=/rootfs'
      - '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)'
    networks:
      - monitoring

volumes:
  prometheus_data:
  grafana_data:

networks:
  monitoring:
    driver: bridge
  proxy:
    external: true

The Compose file marks proxy as an external network, so Compose will not create it. Create it once before starting the stack. If your reverse proxy uses a different network name, change proxy in the Compose file and command to match.

cd /opt/stacks/monitoring
docker network inspect proxy >/dev/null 2>&1 || docker network create proxy
docker compose up -d

Install Node Exporter on each Proxmox node

On every machine you want to monitor (Proxmox hosts, NAS, etc.):

# Download and install Node Exporter
NODE_EXPORTER_VERSION="1.12.1"   # newest release as of 2026-08-22; check github.com/prometheus/node_exporter/releases
wget https://github.com/prometheus/node_exporter/releases/download/v${NODE_EXPORTER_VERSION}/node_exporter-${NODE_EXPORTER_VERSION}.linux-amd64.tar.gz
tar xzf node_exporter-*.tar.gz
mv node_exporter-*/node_exporter /usr/local/bin/

Create a systemd service:

# /etc/systemd/system/node_exporter.service
[Unit]
Description=Prometheus Node Exporter
After=network.target

[Service]
User=nobody
ExecStart=/usr/local/bin/node_exporter
Restart=on-failure

[Install]
WantedBy=multi-user.target
systemctl enable --now node_exporter
# Verify it's up
curl -s http://localhost:9100/metrics | head -5

Add the node’s IP to your Prometheus prometheus.yml scrape config under proxmox-nodes, then reload Prometheus:

curl -X POST http://localhost:9090/-/reload

Proxmox-specific monitoring with pve-exporter

Node Exporter monitors the Proxmox host OS. For per-VM and per-LXC metrics (CPU, RAM, disk per VM), use pve-exporter:

pip3 install prometheus-pve-exporter

# Create a config file
mkdir -p /etc/pve_exporter
cat > /etc/pve_exporter/pve.yml << 'EOF'
default:
  user: pve-monitor@pve
  password: "strong-password-here"
  verify_ssl: false
EOF

Create the Proxmox API user:

pveum user add pve-monitor@pve
pveum passwd pve-monitor@pve
pveum aclmod / -user pve-monitor@pve -role PVEAuditor

Confirm where pip installed the executable:

command -v pve_exporter

Run pve-exporter as a systemd service. The command above should return /usr/local/bin/pve_exporter; if it returns another path, use that path for ExecStart. Current releases take the config path through --config.file (since 3.0.0; 3.10.0 lists no positional arguments in pve_exporter --help):

# /etc/systemd/system/pve_exporter.service
[Unit]
Description=Prometheus Proxmox VE Exporter
Wants=network-online.target
After=network-online.target

[Service]
Type=simple
ExecStart=/usr/local/bin/pve_exporter --config.file=/etc/pve_exporter/pve.yml
Restart=on-failure
RestartSec=5s

[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl enable --now pve_exporter
systemctl --no-pager --full status pve_exporter
ss -ltnp | grep ':9221'

Then add the exporter to your Prometheus scrape config:

  - job_name: "pve"
    static_configs:
      - targets:
          - "192.168.1.10:9221"    # the host where you installed pve-exporter (pve01 here)
    metrics_path: /pve
    params:
      module: [default]
      cluster: ["1"]
      node: ["1"]

Grafana setup

  1. Open Grafana at http://monitoring-host:3000 (or via NPM at grafana.yourdomain.com)
  2. Log in with admin / your configured password
  3. Add a data source: Configuration → Data Sources → Add → Prometheus → URL: http://prometheus:9090

Import dashboards:

Grafana’s dashboard library at grafana.com/grafana/dashboards has ready-made dashboards for Node Exporter.

Import these by ID (Dashboards → Import → enter ID):

  • 1860 — Node Exporter Full (CPU, RAM, disk, filesystem and network panels per node)
  • 10347 — Proxmox via Prometheus (the pve-exporter dashboard). If an older copy of this guide sent you to 7039 (“Node Exporter Full 0.15”) or 10229 (a VictoriaMetrics dashboard), those are the wrong IDs; both checked against grafana.com on 2026-08-22.

After import, set the data source to your Prometheus instance. Dashboard 1860 immediately shows:

  • CPU usage over time per core
  • RAM used, available, cached, buffered
  • Disk IO (reads/writes per second)
  • Filesystem usage with trend
  • Network traffic per interface

Building a custom alert for disk space

The alert in the alerts.yml above fires when any filesystem drops below 15% free. This is a broad catch. For a homelab with a NAS that’s intentionally at 95% capacity (by design), you’d get false alerts. Refine it:

      - alert: DiskAlmostFull
        expr: |
          (node_filesystem_avail_bytes{
            fstype!~"tmpfs|fuse.lxcfs",
            mountpoint!~"/boot.*|/run.*"
          } / node_filesystem_size_bytes) * 100 < 15
          and
          node_filesystem_size_bytes > 10 * 1024^3
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Disk {{ $labels.mountpoint }} at {{ printf \"%.0f\" $value }}% free on {{ $labels.instance }}"

The node_filesystem_size_bytes > 10 * 1024^3 filter only fires the alert for filesystems larger than 10 GiB (the 1024^3 factor) — excluding tiny boot partitions and tmpfs mounts that are supposed to be “full.”

What to monitor beyond nodes

Docker containers. cAdvisor exports per-container CPU, memory, and network metrics to Prometheus. Add it to your Docker Compose stack and import the cAdvisor dashboard (ID: 14282).

UPS status. If you’re running NUT (Network UPS Tools) for your UPS, nut-exporter exposes battery charge, load percentage and UPS status to Prometheus by default; add battery.runtime to its --nut.vars_enable list for estimated runtime. Alert when battery drops below 50% and you have warning time before the power problem becomes a shutdown problem. Not sure how much runtime your UPS actually gives you? The UPS Runtime Calculator estimates runtime at load before you wire up monitoring.

SMART disk health. smartmon-textfile is a shell script that runs smartctl and outputs Prometheus text format. Run it as a cron job and Node Exporter picks it up via the textfile collector. Alert when reallocated sector count is non-zero.

Proxmox backup job status. Write a small exporter that checks each PBS backup job’s last-run status through the PBS API and exposes it as a gauge. Alert when the last backup is older than 25 hours.

What was tested and what was not

This procedure is documentation-derived from the linked Prometheus, Grafana, Node Exporter, Alertmanager, and prometheus-pve-exporter projects. I did not deploy this exact stack end to end and did not measure its RAM or storage footprint; measure yours as described under Resource overhead.

Validate the local IP addresses, firewall rules, TLS choice, credentials, executable paths, external Docker network name, and container image tags in your environment. Check each metrics endpoint and Prometheus target before relying on the alerts.

Resource overhead

Do not size this stack from a generic RAM or storage estimate. Prometheus usage changes with active series, scrape frequency, label cardinality, retention, and query load; Grafana and Alertmanager usage also depends on dashboards, users, and alert traffic.

Measure the deployed containers during a representative workload, then repeat after the Prometheus retention window has filled:

docker stats --no-stream prometheus grafana alertmanager node-exporter
docker system df -v
docker exec prometheus du -sh /prometheus

Use those readings, not an uncited homelab total, to decide whether to change scrape intervals, retention, or host capacity. Docker documents docker stats and docker system df; Prometheus documents its storage controls and operational considerations.


Using Docker for this stack? The Docker Compose Starter Stack covers the baseline services (NPM, Portainer, Uptime Kuma) that complement this monitoring setup.

Sources

Frequently asked questions

How much RAM does the Grafana + Prometheus stack use?
Measure your deployed stack under a representative workload with docker stats –no-stream, inspect Docker storage with docker system df -v, and check the Prometheus data directory before deciding whether the host has enough capacity. There is no universal figure: usage changes with active series, scrape interval, retention, dashboard queries, and the host.
Can I monitor Proxmox specifically with Prometheus?
Yes. Proxmox VE has a built-in Influx and Graphite metrics output, but for Prometheus the common approach is pve-exporter — a Python daemon that scrapes the Proxmox API and exposes metrics in Prometheus format. It exposes VM CPU, RAM, disk IO, and network stats per VM/LXC.
Is Grafana Cloud a better alternative to self-hosting?
Grafana Cloud’s free tier covers 10,000 series, 14 days retention, and 3 users — sufficient for a personal homelab. It eliminates the need to run and maintain the stack yourself. The tradeoff: your metrics go to Grafana Labs’ servers (privacy consideration), and you’re dependent on their availability. For a homelab focused on self-hosting, running your own stack is consistent with the philosophy.
What’s the difference between Prometheus and InfluxDB for homelab monitoring?
Both are time-series databases. Prometheus uses a pull model (it scrapes exporters on a schedule) and stores metrics internally. InfluxDB uses a push model (clients send data to it) and is often paired with Telegraf for collection. Prometheus is common for infrastructure monitoring (Kubernetes, Linux servers). InfluxDB/Telegraf is common for home automation and IoT metrics.
How do I monitor Windows machines with Prometheus?
Use windows_exporter. Download the MSI from the GitHub releases page, install it on the Windows machine, and add a Prometheus scrape config pointing to port 9182. It exposes CPU, memory, disk, and network metrics. Works for Windows 10/11 and Windows Server.

Evidence ledger

Last updated
Methodology
See our methodology for research and review standards.
Update log
  • 2026-09-05 — Page updated.
Corrections
Spotted an error or a stale number? Email hello@techfuelhq.com. Confirmed corrections are added to the update log above.

About the author

Written by Lowell K. Wood IV, who builds and runs TechFuelHQ from St. Louis, Missouri.