Reading time: ~15 minutes Audience: Homelabbers building a monitoring stack from scratch


What Is Grafana + Prometheus?

Overview

Grafana + Prometheus is the de facto standard for cloud-native monitoring. Prometheus collects metrics via HTTP scraping. Grafana visualizes them. Together, they provide a complete observability platform for your homelab: server health, container performance, network traffic, and application metrics.

Stack components: - Prometheus: Time-series database and scraper - Grafana: Visualization and alerting UI - Node Exporter: Exposes Linux hardware and OS metrics - cAdvisor: Exposes Docker container metrics - Alertmanager: Routes fired alerts to notifications

Why This Stack in 2026

The Prometheus ecosystem is larger than ever. Every major homelab tool (Proxmox, TrueNAS, Pi-hole, AdGuard Home, Syncthing) has a Prometheus exporter. Grafana’s dashboard library (grafana.com/dashboards) has 10,000+ pre-built dashboards. The stack is mature, well-documented, and completely free.


Prerequisites

Hardware Requirements

Component Minimum Recommended
CPU 2 cores 4 cores
RAM 2 GB 4 GB
Storage 20 GB 50 GB (for 15-day retention)
Network 1 GbE 1 GbE

Software Requirements

  • Docker Engine 24.0+ and Docker Compose v2+
  • A Linux host (Debian, Ubuntu, or Proxmox VM/LXC)
  • Basic command-line knowledge

Knowledge Prerequisites

  • Docker networking concepts
  • YAML syntax
  • Basic Prometheus query language (PromQL)

Step 1: Create the Docker Compose Stack

Objective

Deploy Prometheus, Grafana, Node Exporter, and cAdvisor with a single docker-compose.yml.

Step-by-Step Instructions

  1. Create a project directory:
mkdir -p ~/monitoring/{prometheus,grafana/config}
cd ~/monitoring
  1. Create docker-compose.yml:
services:
  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    restart: unless-stopped
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus:/etc/prometheus
      - prometheus-data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
      - '--storage.tsdb.retention.time=15d'
      - '--web.console.libraries=/etc/prometheus/console_libraries'
      - '--web.console.templates=/etc/prometheus/consoles'
      - '--web.enable-lifecycle'
    networks:
      - monitoring

  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    restart: unless-stopped
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_USER=admin
      - GF_SECURITY_ADMIN_PASSWORD=***      - GF_USERS_ALLOW_SIGN_UP=false
    volumes:
      - grafana-data:/var/lib/grafana
      - ./grafana/config/datasources.yml:/etc/grafana/provisioning/datasources/datasources.yml
    depends_on:
      - prometheus
    networks:
      - monitoring

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

  cadvisor:
    image: gcr.io/cadvisor/cadvisor:latest
    container_name: cadvisor
    restart: unless-stopped
    privileged: true
    volumes:
      - /:/rootfs:ro
      - /var/run:/var/run:ro
      - /sys:/sys:ro
      - /var/lib/docker:/var/lib/docker:ro
      - /dev/disk:/dev/disk:ro
    devices:
      - /dev/kmsg
    networks:
      - monitoring

volumes:
  prometheus-data:
  grafana-data:

networks:
  monitoring:
    driver: bridge
  1. Create prometheus/prometheus.yml:
global:
  scrape_interval: 15s
  evaluation_interval: 15s

alerting:
  alertmanagers:
    - static_configs:
        - targets: []

rule_files:
  - /etc/prometheus/rules/*.yml

scrape_configs:
  - job_name: 'prometheus'
    static_configs:
      - targets: ['localhost:9090']

  - job_name: 'node-exporter'
    static_configs:
      - targets: ['node-exporter:9100']

  - job_name: 'cadvisor'
    static_configs:
      - targets: ['cadvisor:8080']

  - job_name: 'docker'
    static_configs:
      - targets: ['cadvisor:8080']
  1. Create grafana/config/datasources.yml:
apiVersion: 1

datasources:
  - name: Prometheus
    type: prometheus
    access: proxy
    url: http://prometheus:9090
    isDefault: true
    editable: false
  1. Start the stack:
docker compose up -d
  1. Verify services:
docker compose ps
  1. Access Prometheus at http://your-server:9090
  2. Access Grafana at http://your-server:3000 (admin / your password)

Step 2: Add Alerting Rules

Objective

Create Prometheus alerting rules for common homelab scenarios.

Step-by-Step Instructions

  1. Create prometheus/rules/homelab.yml:
groups:
  - name: homelab-alerts
    interval: 30s
    rules:
      - alert: NodeDown
        expr: up{job="node-exporter"} == 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "Node {{ $labels.instance }} is down"
          description: "Prometheus cannot scrape node-exporter on {{ $labels.instance }}."

      - alert: HighCPUUsage
        expr: 100 - (avg by(instance) (irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 80
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "High CPU usage on {{ $labels.instance }}"
          description: "CPU usage is above 80% for more than 5 minutes."

      - alert: DiskSpaceLow
        expr: (node_filesystem_avail_bytes / node_filesystem_size_bytes) < 0.1
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "Disk space low on {{ $labels.instance }}"
          description: "Less than 10% free on {{ $labels.device }}."

      - alert: MemoryHigh
        expr: (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) > 0.85
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "High memory usage on {{ $labels.instance }}"
          description: "Memory usage is above 85%."

      - alert: ContainerDown
        expr: absent(container_last_seen{name=~"nextcloud|jellyfin|plex|immich"})
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "Critical container is down"
          description: "A critical container is not running."
  1. Reload Prometheus configuration:
curl -X POST http://localhost:9090/-/reload
  1. Check rules in Prometheus UI: Status > Rules

Step 3: Import Dashboards

Objective

Import pre-built dashboards for Node Exporter and cAdvisor.

Step-by-Step Instructions

  1. In Grafana, click + > Import Dashboard
  2. Enter ID 1860 (Node Exporter Full) and click Load
  3. Select the Prometheus data source and click Import
  4. Repeat for ID 14282 (Docker and System Monitoring)

Other useful dashboard IDs:

Dashboard ID Description
Node Exporter Full 1860 Comprehensive Linux metrics
Docker and System 14282 Container + host metrics
Proxmox 10347 Proxmox VE metrics via Prometheus
Pi-hole 10176 DNS query statistics
AdGuard Home 14400 AdGuard Home metrics
Speedtest 13665 Internet speed tracking

Step 4: Add Additional Exporters

Objective

Monitor services that do not expose Prometheus metrics natively.

Step-by-Step Instructions

Pi-hole Exporter:

  pihole-exporter:
    image: ekofr/pihole-exporter:latest
    container_name: pihole-exporter
    environment:
      - PIHOLE_HOSTNAME=192.168.1.10
      - PIHOLE_API_TOKEN=***    ports:
      - "9617:9617"
    networks:
      - monitoring

Add to prometheus.yml:

  - job_name: 'pihole'
    static_configs:
      - targets: ['pihole-exporter:9617']

Proxmox VE Exporter:

  proxmox-exporter:
    image: prompve/prometheus-pve-exporter:latest
    container_name: proxmox-exporter
    environment:
      - PVE_USER=root@pam
      - PVE_PASSWORD=***      - PVE_HOST=192.168.1.5
    ports:
      - "9221:9221"
    networks:
      - monitoring

Add to prometheus.yml:

  - job_name: 'proxmox'
    static_configs:
      - targets: ['proxmox-exporter:9221']

Step 5: Secure the Stack

Objective

Expose Grafana and Prometheus securely via reverse proxy.

Step-by-Step Instructions

  1. Add Traefik or Nginx Proxy Manager to the compose file, or use an existing reverse proxy.

  2. Nginx Proxy Manager configuration:

  3. Add proxy host: grafana.yourdomain.comhttp://grafana:3000
  4. Add proxy host: prometheus.yourdomain.comhttp://prometheus:9090
  5. Request SSL certificates via Let’s Encrypt

  6. Restrict Prometheus access:

  7. Do not expose Prometheus directly to the internet
  8. Use a VPN or internal network for Prometheus access
  9. Grafana can proxy Prometheus queries safely

  10. Enable Grafana HTTPS:

    environment:
      - GF_SERVER_PROTOCOL=https
      - GF_SERVER_CERT_FILE=/etc/grafana/cert.pem
      - GF_SERVER_CERT_KEY=/etc/grafana/key.pem

Pro Tips

Tip 1: Use Recording Rules for Expensive Queries

Queries that aggregate over large time ranges can be slow. Pre-compute them with recording rules:

# prometheus/rules/recordings.yml
groups:
  - name: recordings
    interval: 60s
    rules:
      - record: instance:node_cpu:rate5m
        expr: avg by(instance) (irate(node_cpu_seconds_total{mode!="idle"}[5m]))

Tip 2: Separate Data Retention

Prometheus defaults to 15 days. For long-term storage, use remote_write to VictoriaMetrics or InfluxDB:

# prometheus.yml
remote_write:
  - url: http://victoriametrics:8428/api/v1/write

Tip 3: Backup Your Grafana Dashboards

Dashboards live in the Grafana database. Export them regularly:

# Export all dashboards
curl -s -H "Authorization: Bearer *** http://grafana:3000/api/search | \
  jq -r '.[].uri' | \
  while read uri; do
    curl -s -H "Authorization: Bearer *** "http://grafana:3000/api/dashboards/$uri" > "${uri//\//_}.json"
  done

Troubleshooting Common Issues

Problem 1: Prometheus Targets Show “DOWN

Check: http://prometheus:9090/targets

Fix: - Ensure the target container is on the monitoring network - Check firewall rules on the host - Verify the scrape URL and port are correct

Problem 2: cAdvisor Shows No Containers

Check: Run docker logs cadvisor

Fix: cAdvisor requires privileged mode and access to /var/lib/docker. On Docker rootless, cAdvisor may not work. Use a non-rootless Docker setup or switch to the Docker daemon metrics endpoint.

Problem 3: Grafana “No Data” for Some Panels

Check: Open the panel in edit mode and run the query in Explore.

Fix: The dashboard may use metric names that differ from your exporter version. Update the query or import a newer dashboard version.


Conclusion

Summary

Grafana + Prometheus is the most powerful, flexible, and well-supported monitoring stack for homelabs. With Node Exporter for hardware metrics, cAdvisor for containers, and community dashboards for every tool, you can build a professional-grade observability platform in under an hour.

Next Steps

  1. Deploy the stack on your Proxmox host or a dedicated monitoring VM
  2. Import the Node Exporter Full and Docker dashboards
  3. Add exporters for your critical services (Pi-hole, Proxmox, TrueNAS)
  4. Set up Alertmanager for Slack/Telegram notifications
  5. Backup your Grafana dashboards and Prometheus configuration to Git

Affiliate Opportunities

  • Mini PCs for monitoring: Intel N100 with 16 GB RAM (Amazon)
  • VPS: Hetzner for offsite monitoring (referral)
  • Displays: Wall-mounted tablets for Grafana kiosks (Amazon)

Internal Linking Strategy

  • alertmanager → guide: “prometheus-alertmanager-setup.md”
  • dashboards → guide: “grafana-dashboard-homelab.md”
  • docker-monitoring → guide: “docker-monitoring-grafana-prometheus.md”
  • proxmox-exporter → guide: “proxmox-monitoring-guide.md”

CTA

  • [comment] What does your monitoring stack look like? Share your Grafana screenshot below.
  • [newsletter] Subscribe for our monitoring stack templates and exporter updates.
  • [internal_link] Ready to set up alerting? Read our Alertmanager setup guide next.