Grafana Monitoring: Complete Homelab Guide
Build a comprehensive monitoring stack with Grafana and Prometheus. Dashboards, alerts, and observability.
Introduction
Monitoring is the foundation of a well-maintained homelab. When you’re running multiple services—from media servers to DNS blockers, from Docker containers to custom applications—understanding what’s happening in your infrastructure becomes critical. Grafana provides a powerful, open-source solution for visualizing and analyzing this data.
This guide will walk you through setting up a complete monitoring stack using Grafana, Prometheus, and supporting tools. By the end, you’ll have a production-ready monitoring system with dashboards, alerts, and even AI-powered automation capabilities.
What You’ll Learn:
- Understanding Grafana’s architecture and why it’s perfect for homelabs
- Deploying a complete monitoring stack with Docker Compose
- Creating effective dashboards with real-world queries
- Setting up intelligent alerting and notifications
- Implementing AI automation for anomaly detection and predictive analytics
- Troubleshooting common issues and optimizing performance
Prerequisites:
- Docker and Docker Compose installed on your system
- Basic understanding of YAML syntax and command-line tools
- A homelab server (Raspberry Pi 4+, NAS, or dedicated server)
- Minimum 2GB RAM, 10GB disk space (4GB+ RAM recommended)
Part 1: Understanding the Monitoring Stack
What is Grafana?
Grafana is an open-source visualization and analytics platform that serves as the “dashboard layer” for your monitoring infrastructure. Think of it as a sophisticated control panel that connects to various data sources and presents the information in meaningful ways.
Key Characteristics:
- Open-source and freely available (AGPL license)
- Multi-platform support (Linux, Windows, macOS, ARM devices)
- Lightweight footprint (~100MB base, 200-500MB runtime)
- Extensive plugin ecosystem with 10,000+ community dashboards
- 25+ visualization types for different data presentations
Why Grafana for Homelabs?
Homelabs typically consist of diverse infrastructure: Raspberry Pis, Docker containers, media servers (Jellyfin, Plex), DNS services (Pi-Hole), file servers, network equipment, and IoT devices. Managing these individually is inefficient. Grafana provides:
Centralized Monitoring
- Single interface for all services
- Unified view of system health
- Easy correlation of related metrics
Low Resource Overhead
- Runs well on Raspberry Pi and other low-power hardware
- Efficient memory and CPU usage
- No expensive enterprise licensing
Community Support
- Active homelab community
- Pre-built dashboards for common services
- Extensive documentation and examples
Educational Value
- Learn observability principles
- Master time-series databases
- Understand DevOps practices
- Gain practical experience with monitoring
The LGTM Stack
Modern observability relies on the LGTM Stack:
- Loki: Log aggregation and analysis
- Grafana: Visualization and dashboards
- Tempo: Distributed tracing (optional for homelabs)
- Mimir: Scalable metrics storage (optional for homelabs)
For most homelabs, the essential combination is Prometheus + Grafana + Loki. This provides:
- Prometheus: Metrics collection and storage
- Grafana: Visualization and alerting
- Loki: Log aggregation and analysis
Understanding Data Sources
Prometheus: The Metrics Engine
Prometheus is a time-series database designed for collecting and storing metrics. It uses a “pull-based” model where it periodically scrapes metrics from configured endpoints.
How Prometheus Works:
Services expose metrics → Prometheus scrapes (every 15s) → Stores in TSDB → Grafana queries with PromQL → Visualizes Common Exporters:
- Node Exporter: OS-level metrics (CPU, memory, disk, network)
- cAdvisor: Docker container metrics and resource usage
- Blackbox Exporter: Service availability and latency monitoring
- Custom exporters: Application-specific metrics
Loki: The Logging Layer
Loki is a log aggregation system inspired by Prometheus. Unlike traditional logging solutions, Loki doesn’t index log content—it only indexes labels, making it extremely cost-effective.
Key Benefits:
- Label-based indexing (same labels as Prometheus)
- Compressed storage with configurable retention
- Natural integration with Prometheus metrics
- Correlation between logs and metrics using shared labels
When to Use InfluxDB
While Prometheus handles most homelab use cases, InfluxDB might be better if you have:
- Very high-volume metrics (>1M points/second)
- High-cardinality data (many unique label combinations)
- Push-based metric collection requirements
For this guide, we’ll focus on Prometheus as it’s simpler and more suitable for typical homelab scenarios.
Part 2: Deploying Your Monitoring Stack
Quick Start with Docker Compose
We’ll deploy a complete monitoring stack using Docker Compose. This includes Grafana, Prometheus, Node Exporter (for system metrics), and cAdvisor (for container metrics).
Create Project Structure:
mkdir -p ~/grafana-monitoring
cd ~/grafana-monitoring Create docker-compose.yml:
version: '3.8'
services:
# Time-Series Database for Metrics
prometheus:
image: prom/prometheus:latest
container_name: prometheus
restart: unless-stopped
ports:
- '9090:9090'
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus-storage:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--storage.tsdb.retention.time=30d'
- '--web.enable-lifecycle'
networks:
- monitoring
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:9090/-/healthy"]
interval: 30s
timeout: 10s
retries: 3
# System Metrics Collector
node-exporter:
image: prom/node-exporter:latest
container_name: node-exporter
restart: unless-stopped
ports:
- '9100:9100'
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
# Container Metrics Collector
cadvisor:
image: gcr.io/cadvisor/cadvisor:latest
container_name: cadvisor
restart: unless-stopped
ports:
- '8080:8080'
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
- /dev/disk/:/dev/disk:ro
networks:
- monitoring
privileged: true
devices:
- /dev/kmsg
# Visualization and Dashboarding
grafana:
image: grafana/grafana:latest
container_name: grafana
restart: unless-stopped
ports:
- '3000:3000'
environment:
# Security Configuration
- GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:-ChangeMeNow123!}
- GF_SECURITY_ADMIN_USER=admin
- GF_SECURITY_COOKIE_SECURE=false
- GF_SECURITY_DISABLE_BRUTE_FORCE_LOGIN_PROTECTION=false
# User Management
- GF_USERS_ALLOW_SIGN_UP=false
- GF_USERS_DEFAULT_THEME=dark
# Plugin Installation (optional)
- GF_INSTALL_PLUGINS=grafana-piechart-panel
volumes:
- grafana-storage:/var/lib/grafana
- ./provisioning:/etc/grafana/provisioning
networks:
- monitoring
depends_on:
prometheus:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:3000/api/health"]
interval: 30s
timeout: 10s
retries: 3
volumes:
prometheus-storage:
driver: local
grafana-storage:
driver: local
networks:
monitoring:
driver: bridge
ipam:
config:
- subnet: 172.28.0.0/16 Create prometheus.yml:
global:
scrape_interval: 15s # How often to scrape targets
evaluation_interval: 15s # How often to evaluate rules
external_labels:
monitor: 'homelab'
cluster: 'main'
# Scrape Configurations
scrape_configs:
# Monitor Prometheus itself
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
labels:
service: 'prometheus'
# System metrics from Node Exporter
- job_name: 'node-exporter'
static_configs:
- targets: ['node-exporter:9100']
labels:
instance: 'homelab-server'
environment: 'production'
# Container metrics from cAdvisor
- job_name: 'cadvisor'
static_configs:
- targets: ['cadvisor:8080']
labels:
instance: 'homelab-server'
# Add your additional services here
# Example: Pi-Hole DNS monitoring
# - job_name: 'pihole'
# scrape_interval: 30s
# static_configs:
# - targets: ['192.168.1.5:9617']
# labels:
# service: 'dns-adblock' Create .env file (optional, for custom admin password):
GRAFANA_ADMIN_PASSWORD=YourSecurePasswordHere Deploying the Stack
Start all services:
docker-compose up -d Verify everything is running:
docker-compose ps You should see all four services in the “Up” state:
NAME STATUS PORTS
grafana Up 0.0.0.0:3000->3000/tcp
prometheus Up 0.0.0.0:9090->9090/tcp
node-exporter Up 0.0.0.0:9100->9100/tcp
cadvisor Up 0.0.0.0:8080->8080/tcp Check logs for any errors:
docker-compose logs -f grafana Access your services:
- Grafana: http://localhost:3000 (admin / ChangeMeNow123!)
- Prometheus: http://localhost:9090
- Node Exporter: http://localhost:9100/metrics
- cAdvisor: http://localhost:8080
Initial Grafana Configuration
- Log in to Grafana at http://localhost:3000
- Change the admin password (Settings → Profile → Change Password)
- Add Prometheus data source:
- Navigate to Configuration → Data Sources
- Click Add data source
- Select Prometheus
- Configure:
- Name: Prometheus
- URL: http://prometheus:9090
- Access: Server (default)
- Click Save & Test
- You should see “Data source is working”
Part 3: Building Effective Dashboards
Dashboard Design Principles
Before creating dashboards, understand these key principles:
- Purpose First: Each dashboard should answer specific questions
- Progressive Disclosure: Show summary first, allow drilling down for details
- Context Matters: Include time ranges, alert status, and metadata
- Consistent Layout: Group related panels logically
- Performance: Optimize queries to avoid slow load times
Creating Your First Dashboard
Let’s build a comprehensive “Homelab Overview” dashboard.
Step 1: Create New Dashboard
- Click Dashboards in left menu
- Click + Create Dashboard
- Click + Add visualization
Step 2: CPU Usage Panel
Select Prometheus as data source and enter this query:
100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) Explanation: This calculates CPU usage percentage by:
- Taking the rate of idle CPU time over 5 minutes
- Averaging across all CPU cores
- Converting to percentage
- Subtracting from 100 to get “used” percentage
Configuration:
- Panel title: “CPU Usage”
- Visualization: Time series graph
- Unit: Percent (0-100)
- Thresholds:
- Green: < 50
- Yellow: 50-80
- Red: > 80
- Legend: Show as table
- Tooltip mode: All series
Step 3: Memory Usage Panel
Add new panel with this query:
(1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100 Explanation: Calculates memory usage by:
- Dividing available memory by total memory
- Subtracting from 1 to get “used” ratio
- Multiplying by 100 for percentage
Configuration:
- Panel title: “Memory Usage”
- Visualization: Gauge
- Unit: Percent (0-100)
- Thresholds:
- Green: < 70
- Yellow: 70-85
- Red: > 85
Step 4: Disk Space Panel
(node_filesystem_avail_bytes{fstype!~"tmpfs|fuse.*|squashfs|vfat"}
/ node_filesystem_size_bytes{fstype!~"tmpfs|fuse.*|squashfs|vfat"}) * 100 Explanation: Shows available disk space by:
- Excluding temporary filesystems (tmpfs, fuse, etc.)
- Calculating ratio of available to total space
- Converting to percentage
Configuration:
- Panel title: “Disk Space Available”
- Visualization: Bar gauge
- Unit: Percent (0-100)
- Display labels: Show filesystem mount points
- Thresholds:
- Green: > 20
- Yellow: 10-20
- Red: < 10
Step 5: Network Traffic Panel
# Receive rate (bytes per second)
rate(node_network_receive_bytes_total{device!~"lo|docker.*|veth.*"}[5m])
# Transmit rate (bytes per second)
rate(node_network_transmit_bytes_total{device!~"lo|docker.*|veth.*"}[5m]) Explanation: Tracks network traffic by:
- Excluding loopback and virtual interfaces
- Calculating per-second rate over 5 minutes
- Showing both receive and transmit separately
Configuration:
- Panel title: “Network Traffic”
- Visualization: Time series graph
- Unit: Bytes/sec
- Multiple series: Show RX and TX on same graph
- Legend: Table format with current/max values
Step 6: Container Resource Usage
# Container memory usage in MB
container_memory_usage_bytes{name!=""} / 1024 / 1024
# Container CPU usage percentage
rate(container_cpu_usage_seconds_total{name!=""}[5m]) * 100 Configuration:
- Panel title: “Container Resources”
- Visualization: Table
- Show both CPU and memory columns
- Sort by memory usage (descending)
- Format: CPU as percentage, Memory as MB
Step 7: Service Uptime
# Uptime percentage over last 24 hours
avg_over_time(up[24h]) * 100 Configuration:
- Panel title: “Service Availability (24h)”
- Visualization: Stat
- Unit: Percent (0-100)
- Display: Value and sparkline
- Thresholds:
- Green: > 99.5
- Yellow: 99-99.5
- Red: < 99
Step 8: Save Dashboard
- Click Save dashboard (disk icon)
- Name: “Homelab Overview”
- Add description: “Primary monitoring dashboard for homelab infrastructure”
- Set folder: Create “Homelab” folder
- Save
Dashboard Variables and Templates
Variables make dashboards reusable across different hosts or time ranges.
Add Host Variable:
- Click Dashboard settings (gear icon)
- Select Variables
- Click + New variable
- Configure:
- Name:
host - Type: Query
- Label: Host
- Data source: Prometheus
- Query:
label_values(node_uname_info, nodename) - Regex: (leave empty)
- Sort: Alphabetical (asc)
- Multi-select: Enable
- Include All option: Enable
- Name:
- Click Apply
Add Interval Variable:
- Click + New variable
- Configure:
- Name:
interval - Type: Interval
- Label: Refresh Interval
- Values:
1m,5m,15m,30m,1h,6h,12h,1d - Auto option: Enable
- Name:
- Click Apply
Use Variables in Queries:
Modify your CPU query to use the host variable:
100 - (avg by(instance) (rate(node_cpu_seconds_total{instance=~"$host.*", mode="idle"}[$interval])) * 100) Now you can:
- Select specific hosts from dropdown
- Change time interval dynamically
- Share dashboard configuration across multiple servers
Importing Pre-Built Dashboards
The Grafana community provides thousands of pre-built dashboards. Here are essential ones for homelabs:
Node Exporter Full (ID: 1860)
- Dashboards → Import
- Enter dashboard ID:
1860 - Click Load
- Select Prometheus data source
- Click Import
This dashboard provides comprehensive Linux system metrics including:
- CPU, Memory, Disk, Network
- System load and processes
- File system statistics
- Detailed performance metrics
Docker Container & Host Metrics (ID: 11600)
- Import ID:
11600 - Provides Docker-specific monitoring
- Container resource usage
- Host and container correlations
Customizing Imported Dashboards:
- After import, click Edit on any panel
- Modify queries to match your setup
- Adjust thresholds for your hardware
- Remove unnecessary panels
- Save As with new name to preserve original
Part 4: Intelligent Alerting and Notifications
Understanding Alert Architecture
Grafana’s alerting system works like this:
Prometheus evaluates metric → Condition met → Alert fires
↓
Grafana Alert Manager receives alert
↓
Matches notification policy
↓
Routes to contact point (Email, Slack, Discord)
↓
User receives notification Setting Up Contact Points
Configure Email Alerts:
- Navigate to Alerting → Contact points
- Click + Create contact point
- Configure:
- Name: Email Alerts
- Integration: Email
- Addresses: [email protected]
- Include image: Enable (attaches dashboard snapshot)
- Click Test to verify
- Click Save contact point
Configure Slack Alerts:
Create Slack webhook:
- Go to https://api.slack.com/apps
- Create app → Incoming Webhooks
- Copy webhook URL
In Grafana:
- Alerting → Contact points
- + Create contact point
- Configure:
- Name: Slack Alerts
- Integration: Slack
- Webhook URL: Paste your webhook URL
- Username: Grafana Alert Bot
- Channel: #homelab-alerts
- Test and save
Configure Discord Alerts:
Create Discord webhook:
- Server Settings → Integrations → Webhooks
- Create webhook, copy URL
In Grafana:
- + Create contact point
- Integration: Discord
- Webhook URL: Paste Discord webhook
- Message title: Homelab Alert
- Test and save
Creating Essential Alert Rules
Alert 1: High CPU Usage
- Alerting → Alert rules
- + Create alert rule
- Configure:
Rule name: High CPU Usage Query:
100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) Condition: WHEN last() OF query(A) IS ABOVE 80
Evaluation behavior:
- Folder: Homelab Alerts
- Evaluation group: System Resources
- Evaluation interval: 1m
- Pending period: 5m (wait 5 minutes before firing)
Annotations:
- Summary:
High CPU usage on [[ $labels.instance ]] - Description:
CPU usage is [[ $value ]]% on [[ $labels.instance ]] - Runbook URL: (link to your troubleshooting docs)
Labels:
- severity: warning
- component: cpu
- Click Save rule and exit
Alert 2: High Memory Usage
(1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100 Condition: IS ABOVE 85 Pending period: 5m Annotations:
- Summary:
High memory usage on [[ $labels.instance ]] - Description:
Memory usage is [[ $value ]]% on [[ $labels.instance ]]Labels: - severity: warning
- component: memory
Alert 3: Low Disk Space
(node_filesystem_avail_bytes{fstype!~"tmpfs|fuse.*"}
/ node_filesystem_size_bytes{fstype!~"tmpfs|fuse.*"}) * 100 Condition: IS BELOW 15 Pending period: 10m Annotations:
- Summary:
Low disk space on [[ $labels.instance ]] - Description:
Only [[ $value ]]% disk space remaining on [[ $labels.mountpoint ]]Labels: - severity: critical
- component: disk
Alert 4: Service Down
up{job="prometheus"} Condition: IS BELOW 1 (0 means down) Pending period: 1m Annotations:
- Summary:
Service [[ $labels.job ]] is down - Description:
[[ $labels.instance ]] has been unreachable for 1 minuteLabels: - severity: critical
- component: availability
Alert 5: Container Restart
increase(container_start_time_seconds{name!=""}[5m]) > 0 Condition: IS ABOVE 0 Pending period: 1m Annotations:
- Summary:
Container [[ $labels.name ]] restarted - Description:
Container restarted, possible crash or manual restartLabels: - severity: info
- component: containers
Configuring Notification Policies
Notification policies control how alerts are routed to different contact points based on labels.
- Alerting → Notification policies
- Edit Default policy:
Group by: ['alertname', 'instance', 'severity']
Group wait: 30s # Wait 30s to batch related alerts
Group interval: 5m # Send group update every 5m
Repeat interval: 1h # Repeat unresolved alerts every hour - Add child policies for different severities:
Critical Alerts Policy:
- Matching labels: severity = critical
- Contact point: Email Alerts + Slack Alerts
- Group wait: 10s (faster notification)
- Repeat interval: 30m (more frequent)
Warning Alerts Policy:
- Matching labels: severity = warning
- Contact point: Slack Alerts
- Group wait: 5m
- Repeat interval: 4h
Info Alerts Policy:
- Matching labels: severity = info
- Contact point: Slack Alerts (separate channel)
- Group wait: 15m
- Repeat interval: Never (don’t repeat)
Alert Silencing and Maintenance Windows
Create Silence for Maintenance:
- Alerting → Silences
- + Create silence
- Configure:
- Matchers: instance = “homelab-server”
- Duration: 2 hours
- Comment: “Scheduled maintenance - system upgrade”
- Creator: your-name
- Click Submit
Alerts matching the criteria won’t fire during the silence period.
Part 5: AI Automation and Advanced Features
Anomaly Detection with Machine Learning
Let’s implement intelligent threshold detection using machine learning. This Python script uses historical data from Prometheus to automatically determine appropriate alert thresholds.
Create anomaly_detection.py:
#!/usr/bin/env python3
"""
Grafana Anomaly Detection
Automatically detects unusual patterns in metrics using machine learning
"""
import requests
import numpy as np
from sklearn.ensemble import IsolationForest
from sklearn.preprocessing import StandardScaler
import time
from datetime import datetime
class AnomalyDetector:
def __init__(self, prometheus_url='http://localhost:9090'):
self.prom_url = prometheus_url
self.scaler = StandardScaler()
def fetch_metrics(self, query, duration_days=7):
"""Fetch historical metrics from Prometheus"""
url = f'{self.prom_url}/api/v1/query_range'
end_time = int(time.time())
start_time = end_time - (duration_days * 86400)
params = {
'query': query,
'start': start_time,
'end': end_time,
'step': '1h' # 1 hour resolution
}
response = requests.get(url, params=params, timeout=30)
response.raise_for_status()
data = response.json()
if not data['data']['result']:
raise ValueError(f"No data returned for query: {query}")
values = data['data']['result'][0]['values']
return np.array([float(v[1]) for v in values])
def detect_anomalies(self, data, contamination=0.05):
"""
Detect anomalies using Isolation Forest algorithm
contamination: Expected proportion of outliers (5% default)
"""
data_scaled = self.scaler.fit_transform(data.reshape(-1, 1))
clf = IsolationForest(
contamination=contamination,
random_state=42,
n_estimators=100
)
predictions = clf.fit_predict(data_scaled)
# -1 for anomalies, 1 for normal
return predictions
def calculate_dynamic_threshold(self, data, percentile=95):
"""
Calculate recommended threshold based on historical data
Uses 95th percentile + margin for safety
"""
base_threshold = np.percentile(data, percentile)
margin = np.std(data) * 0.5 # Half standard deviation as buffer
return base_threshold + margin
def analyze_metric(self, metric_name, query):
"""Complete analysis of a metric"""
print(f"\n{'='*60}")
print(f"Analyzing: {metric_name}")
print(f"{'='*60}")
try:
# Fetch data
data = self.fetch_metrics(query)
print(f"✓ Fetched {len(data)} data points")
# Detect anomalies
anomalies = self.detect_anomalies(data)
anomaly_count = np.sum(anomalies == -1)
anomaly_pct = (anomaly_count / len(data)) * 100
print(f"✓ Found {anomaly_count} anomalies ({anomaly_pct:.2f}%)")
# Calculate statistics
stats = {
'mean': np.mean(data),
'median': np.median(data),
'std': np.std(data),
'min': np.min(data),
'max': np.max(data),
'p95': np.percentile(data, 95),
'p99': np.percentile(data, 99)
}
# Calculate recommended threshold
threshold = self.calculate_dynamic_threshold(data)
# Display results
print(f"\nStatistics:")
print(f" Mean: {stats['mean']:.2f}")
print(f" Median: {stats['median']:.2f}")
print(f" Std Dev: {stats['std']:.2f}")
print(f" Min/Max: {stats['min']:.2f} / {stats['max']:.2f}")
print(f" 95th percentile: {stats['p95']:.2f}")
print(f" 99th percentile: {stats['p99']:.2f}")
print(f"\n🎯 Recommended Alert Threshold: {threshold:.2f}")
# Find anomaly times
if anomaly_count > 0:
anomaly_indices = np.where(anomalies == -1)[0]
print(f"\nAnomaly detected at indices: {anomaly_indices[:5].tolist()}...")
return {
'metric': metric_name,
'statistics': stats,
'recommended_threshold': threshold,
'anomaly_count': anomaly_count,
'anomaly_percentage': anomaly_pct
}
except Exception as e:
print(f"✗ Error: {str(e)}")
return None
# Example usage
if __name__ == '__main__':
detector = AnomalyDetector()
# Analyze CPU usage
cpu_result = detector.analyze_metric(
'CPU Usage',
'100 - (avg(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)'
)
# Analyze memory usage
mem_result = detector.analyze_metric(
'Memory Usage',
'(1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100'
)
# Analyze disk usage
disk_result = detector.analyze_metric(
'Disk Usage',
'(1 - (node_filesystem_avail_bytes / node_filesystem_size_bytes)) * 100'
)
print(f"\n{'='*60}")
print("Analysis Complete")
print(f"{'='*60}\n") Install dependencies:
pip3 install requests numpy scikit-learn Run the analysis:
python3 anomaly_detection.py This will output recommended alert thresholds based on your actual usage patterns rather than arbitrary values.
Predictive Analytics for Capacity Planning
This script predicts when resources will run out based on historical trends.
Create capacity_planning.py:
#!/usr/bin/env python3
"""
Predictive Capacity Planning
Forecast when resources will be exhausted
"""
import requests
import numpy as np
from sklearn.linear_model import LinearRegression
from datetime import datetime, timedelta
import time
class CapacityPlanner:
def __init__(self, prometheus_url='http://localhost:9090'):
self.prom_url = prometheus_url
def fetch_metrics(self, query, duration_days=30):
"""Fetch 30 days of historical data"""
url = f'{self.prom_url}/api/v1/query_range'
end_time = int(time.time())
start_time = end_time - (duration_days * 86400)
params = {
'query': query,
'start': start_time,
'end': end_time,
'step': '6h' # 6 hour resolution
}
response = requests.get(url, params=params, timeout=30)
response.raise_for_status()
data = response.json()
values = data['data']['result'][0]['values']
return np.array([float(v[1]) for v in values])
def predict_exhaustion(self, data, threshold=90, days_ahead=30):
"""
Predict when resource will exceed threshold
Returns days until exhaustion or None
"""
# Prepare data
X = np.arange(len(data)).reshape(-1, 1)
y = data
# Fit linear regression model
model = LinearRegression()
model.fit(X, y)
# Predict future
hours_ahead = days_ahead * 4 # 6-hour intervals
future_X = np.arange(len(data), len(data) + hours_ahead).reshape(-1, 1)
predictions = model.predict(future_X)
# Find threshold crossing
crossing_indices = np.where(predictions > threshold)[0]
if len(crossing_indices) > 0:
first_crossing = crossing_indices[0]
hours_until = first_crossing * 6 # 6-hour intervals
days_until = hours_until / 24
crossing_date = datetime.now() + timedelta(days=days_until)
return {
'will_exhaust': True,
'days_until': days_until,
'crossing_date': crossing_date.strftime('%Y-%m-%d %H:%M'),
'predicted_peak': np.max(predictions),
'current_trend': model.coef_[0],
'r_squared': model.score(X, y)
}
return {
'will_exhaust': False,
'predicted_peak': np.max(predictions),
'current_trend': model.coef_[0],
'r_squared': model.score(X, y)
}
def analyze_capacity(self, resource_name, query, threshold=90):
"""Analyze capacity for a specific resource"""
print(f"\n{'='*60}")
print(f"Capacity Analysis: {resource_name}")
print(f"{'='*60}")
try:
data = self.fetch_metrics(query)
current_value = data[-1]
print(f"Current value: {current_value:.2f}%")
print(f"Alert threshold: {threshold}%")
result = self.predict_exhaustion(data, threshold)
print(f"\nTrend: {result['current_trend']:.4f}% per 6 hours")
print(f"Model accuracy (R²): {result['r_squared']:.4f}")
print(f"Predicted peak (30 days): {result['predicted_peak']:.2f}%")
if result['will_exhaust']:
print(f"\n⚠️ WARNING: {resource_name} will exceed {threshold}%")
print(f" Estimated date: {result['crossing_date']}")
print(f" Days until: {result['days_until']:.1f}")
print(f"\n Recommended actions:")
if 'disk' in resource_name.lower():
print(f" - Clean up old logs and temporary files")
print(f" - Archive or delete unused data")
print(f" - Consider adding storage capacity")
elif 'memory' in resource_name.lower():
print(f" - Identify memory-hungry processes")
print(f" - Optimize application memory usage")
print(f" - Consider adding RAM")
else:
print(f"\n✓ {resource_name} trending normally")
print(f" No capacity issues expected in next 30 days")
return result
except Exception as e:
print(f"✗ Error: {str(e)}")
return None
# Example usage
if __name__ == '__main__':
planner = CapacityPlanner()
# Analyze disk capacity
disk_result = planner.analyze_capacity(
'Disk Usage',
'(1 - (node_filesystem_avail_bytes{fstype!~"tmpfs|fuse.*"} / node_filesystem_size_bytes{fstype!~"tmpfs|fuse.*"})) * 100',
threshold=90
)
# Analyze memory capacity
mem_result = planner.analyze_capacity(
'Memory Usage',
'(1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100',
threshold=85
)
print(f"\n{'='*60}")
print("Capacity Planning Complete")
print(f"{'='*60}\n") Run capacity planning:
python3 capacity_planning.py Auto-Remediation with Webhooks
Create an automated response system that can fix common issues automatically.
Create auto_remediation.py:
#!/usr/bin/env python3
"""
Auto-Remediation Service
Automatically responds to alerts and fixes common issues
"""
from flask import Flask, request, jsonify
import subprocess
import logging
import json
app = Flask(__name__)
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def restart_container(container_name):
"""Restart a Docker container"""
try:
result = subprocess.run(
['docker', 'restart', container_name],
capture_output=True,
text=True,
timeout=30
)
if result.returncode == 0:
logger.info(f"✓ Restarted container: {container_name}")
return True
else:
logger.error(f"✗ Failed to restart {container_name}: {result.stderr}")
return False
except Exception as e:
logger.error(f"✗ Error restarting container: {str(e)}")
return False
def cleanup_docker():
"""Clean up Docker resources"""
try:
# Remove unused images
subprocess.run(['docker', 'image', 'prune', '-f'], timeout=60)
# Remove unused volumes
subprocess.run(['docker', 'volume', 'prune', '-f'], timeout=60)
# Remove stopped containers
subprocess.run(['docker', 'container', 'prune', '-f'], timeout=60)
logger.info("✓ Docker cleanup completed")
return True
except Exception as e:
logger.error(f"✗ Docker cleanup failed: {str(e)}")
return False
def clear_logs():
"""Clear system logs"""
try:
subprocess.run(['journalctl', '--vacuum-time=7d'], timeout=60)
logger.info("✓ Cleared old system logs")
return True
except Exception as e:
logger.error(f"✗ Log cleanup failed: {str(e)}")
return False
@app.route('/webhook/alert', methods=['POST'])
def handle_alert():
"""Handle incoming alerts from Grafana"""
try:
alert_data = request.json
logger.info(f"Received alert: {json.dumps(alert_data, indent=2)}")
if alert_data.get('status') == 'firing':
alerts = alert_data.get('alerts', [])
for alert in alerts:
labels = alert.get('labels', {})
annotations = alert.get('annotations', {})
alert_name = labels.get('alertname')
severity = labels.get('severity')
component = labels.get('component')
logger.info(f"Processing: {alert_name} (severity: {severity})")
# Auto-remediation logic
actions_taken = []
# High disk usage - cleanup
if component == 'disk' and severity in ['critical', 'warning']:
logger.info("Taking action: Disk cleanup")
if cleanup_docker():
actions_taken.append("Docker cleanup")
if clear_logs():
actions_taken.append("Log cleanup")
# Service down - restart container
if component == 'availability':
job_name = labels.get('job')
if job_name:
logger.info(f"Taking action: Restart service {job_name}")
if restart_container(job_name):
actions_taken.append(f"Restarted {job_name}")
# Container issues - restart
if component == 'containers':
container_name = labels.get('name')
if container_name:
logger.info(f"Taking action: Restart container {container_name}")
if restart_container(container_name):
actions_taken.append(f"Restarted {container_name}")
# Log actions
if actions_taken:
logger.info(f"✓ Actions completed: {', '.join(actions_taken)}")
else:
logger.info("No automated actions available for this alert")
return jsonify({'status': 'ok', 'message': 'Alert processed'}), 200
except Exception as e:
logger.error(f"Error processing alert: {str(e)}")
return jsonify({'status': 'error', 'message': str(e)}), 500
@app.route('/health', methods=['GET'])
def health_check():
"""Health check endpoint"""
return jsonify({'status': 'healthy'}), 200
if __name__ == '__main__':
logger.info("Starting auto-remediation service on port 5000")
app.run(host='0.0.0.0', port=5000, debug=False) Install dependencies:
pip3 install flask Run the service:
python3 auto_remediation.py Configure Grafana to use webhook:
Alerting → Contact points
+ Create contact point
Configure:
- Name: Auto Remediation
- Integration: Webhook
- URL: http://localhost:5000/webhook/alert
- HTTP Method: POST
Test and save
Update notification policies to include auto-remediation for critical alerts
Grafana Beyla: Auto-Instrumentation with eBPF
Grafana Beyla automatically instruments applications without code changes using eBPF (Extended Berkeley Packet Filter).
Add to docker-compose.yml:
beyla:
image: grafana/beyla:latest
container_name: beyla
privileged: true
pid: host
restart: unless-stopped
volumes:
- /proc:/proc:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
environment:
- BEYLA_OPEN_PORT=8080 # Port to instrument
- BEYLA_METRICS_PORT=9090 # Export to Prometheus
- BEYLA_SERVICE_NAME=homelab-services
- BEYLA_PRINT_TRACES=true # Debug output
networks:
- monitoring Add Beyla to Prometheus configuration:
- job_name: 'beyla'
static_configs:
- targets: ['beyla:9090']
labels:
service: 'beyla' Restart services:
docker-compose up -d Beyla will now automatically instrument any HTTP/gRPC services and expose RED metrics (Rate, Errors, Duration).
Part 6: Troubleshooting and Optimization
Common Issues and Solutions
Issue: Grafana can’t connect to Prometheus
Symptoms:
- “Connection refused” error
- Dashboards show “No data”
- Red X on data source
Solution:
# 1. Verify Prometheus is running
docker ps | grep prometheus
# 2. Test connectivity from Grafana container
docker exec grafana curl http://prometheus:9090
# 3. Check Prometheus targets
curl http://localhost:9090/api/v1/targets
# 4. Verify data source URL in Grafana
# Should be: http://prometheus:9090 (NOT localhost:9090) Issue: High CPU or memory usage
Symptoms:
- Containers using 100% CPU
- Out of memory errors
- Slow dashboard loading
Solutions:
# Check resource usage
docker stats grafana prometheus
# View logs for errors
docker logs -f grafana
# Optimize Prometheus retention
# Edit prometheus.yml command section:
- '--storage.tsdb.retention.time=7d' # Reduce from 30d
- '--storage.tsdb.retention.size=2GB' # Add size limit
# Restart Prometheus
docker-compose restart prometheus Issue: Missing metrics
Symptoms:
- Query returns “no data”
- Some exporters not reporting
Solutions:
# List all available metrics
curl 'http://localhost:9090/api/v1/label/__name__/values' | jq .
# Check if exporter is accessible
curl http://localhost:9100/metrics
# Verify Prometheus scrape config
docker exec prometheus cat /etc/prometheus/prometheus.yml
# Check Prometheus targets status
curl http://localhost:9090/api/v1/targets | jq . Issue: Alerts not firing
Symptoms:
- Rules created but never fire
- No notifications received
Solutions:
# Check alert rules
curl http://localhost:9090/api/v1/rules | jq .
# View Grafana logs
docker logs grafana | grep -i alert
# Test alert condition manually
# In Grafana: Alerting → Alert rules → [Your Rule] → Test Query Optimization
Slow queries vs. Fast queries:
# SLOW: No label filters
container_memory_usage_bytes
# BETTER: Filter by job
container_memory_usage_bytes{job="docker"}
# BEST: Specific label matchers
container_memory_usage_bytes{job="docker", instance="homelab-server"} Recording Rules for Performance:
Create prometheus-rules.yml:
groups:
- name: performance_rules
interval: 30s
rules:
# Pre-calculate CPU usage
- record: instance:cpu_usage:rate5m
expr: 100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)
# Pre-calculate memory usage
- record: instance:memory_usage:percent
expr: (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100
# Pre-calculate disk usage
- record: instance:disk_usage:percent
expr: (1 - (node_filesystem_avail_bytes / node_filesystem_size_bytes)) * 100 Update docker-compose.yml:
prometheus:
volumes:
- ./prometheus-rules.yml:/etc/prometheus/prometheus-rules.yml:ro
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--web.enable-lifecycle' Update prometheus.yml:
rule_files:
- 'prometheus-rules.yml' Now use pre-calculated metrics in dashboards:
# Instead of complex query, use recording rule
instance:cpu_usage:rate5m Backup and Recovery
Backup script (backup-grafana.sh):
#!/bin/bash
# Grafana Monitoring Stack Backup
BACKUP_DIR="/backups/grafana"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
echo "Starting backup at $TIMESTAMP"
# Create backup directory
mkdir -p "$BACKUP_DIR"
# Backup Grafana data
echo "Backing up Grafana..."
docker run --rm -v grafana-storage:/data -v "$BACKUP_DIR":/backup alpine tar czf "/backup/grafana_${TIMESTAMP}.tar.gz" -C /data .
# Backup Prometheus data (optional - can regenerate)
echo "Backing up Prometheus..."
docker run --rm -v prometheus-storage:/data -v "$BACKUP_DIR":/backup alpine tar czf "/backup/prometheus_${TIMESTAMP}.tar.gz" -C /data .
# Backup configurations
echo "Backing up configurations..."
cp docker-compose.yml "$BACKUP_DIR/docker-compose_${TIMESTAMP}.yml"
cp prometheus.yml "$BACKUP_DIR/prometheus_${TIMESTAMP}.yml"
[ -f prometheus-rules.yml ] && cp prometheus-rules.yml "$BACKUP_DIR/prometheus-rules_${TIMESTAMP}.yml"
# Retention: Keep last 7 days
echo "Cleaning old backups..."
find "$BACKUP_DIR" -name "*.tar.gz" -mtime +7 -delete
find "$BACKUP_DIR" -name "*.yml" -mtime +7 -delete
echo "Backup completed: $BACKUP_DIR"
ls -lh "$BACKUP_DIR"/*${TIMESTAMP}* Make executable and run:
chmod +x backup-grafana.sh
./backup-grafana.sh Automate with cron:
# Edit crontab
crontab -e
# Add daily backup at 2 AM
0 2 * * * /home/user/backup-grafana.sh >> /var/log/grafana-backup.log 2>&1 Recovery script (restore-grafana.sh):
#!/bin/bash
# Restore Grafana from backup
if [ -z "$1" ]; then
echo "Usage: $0 <backup-file>"
echo "Example: $0 grafana_20241216_020000.tar.gz"
exit 1
fi
BACKUP_FILE="$1"
BACKUP_DIR="/backups/grafana"
echo "Stopping containers..."
docker-compose down
echo "Restoring Grafana data..."
docker run --rm -v grafana-storage:/data -v "$BACKUP_DIR":/backup alpine tar xzf "/backup/$BACKUP_FILE" -C /data
echo "Starting containers..."
docker-compose up -d
echo "Restore complete!" Conclusion
You now have a complete, production-ready monitoring stack for your homelab. This guide has covered:
Core Capabilities:
- Full monitoring stack with Grafana, Prometheus, and exporters
- Comprehensive dashboards for system and container metrics
- Intelligent alerting with multiple notification channels
- AI-powered anomaly detection and capacity planning
- Auto-remediation for common issues
- Backup and recovery procedures
Your Next Steps:
This Week:
- Deploy the Docker Compose stack
- Create your first dashboard with CPU, memory, disk, and network panels
- Set up 5 essential alerts
- Configure at least one notification channel
This Month: 5. Add monitoring for your specific services (Pi-Hole, Jellyfin, etc.) 6. Implement log aggregation with Loki 7. Run the anomaly detection script to optimize alert thresholds 8. Set up automated backups
This Quarter: 9. Deploy Grafana Beyla for application auto-instrumentation 10. Implement predictive capacity planning 11. Create auto-remediation workflows for common issues 12. Share your dashboards with the community
Key Takeaways:
- Start simple and expand gradually
- Use community dashboards as templates
- Optimize alert thresholds based on your actual usage patterns
- Automate what can be automated
- Document your setup and procedures
- Regularly test backups and alerts
Resources:
- Grafana Documentation
- Prometheus Documentation
- Grafana Community Dashboards
- PromQL Tutorial
- r/homelab Community
Support: If you encounter issues or have questions, the homelab and Grafana communities are incredibly helpful. Share your setup, learn from others, and contribute back when you can.
Happy monitoring!
Guide Version: 1.0 Last Updated: December 2024 Target Audience: Homelab enthusiasts, self-hosted infrastructure operators Difficulty Level: Intermediate Estimated Time: 45 minutes + hands-on practice