Monitoring Stack Deployment
This guide covers the deployment of a centralized monitoring and logging stack using Grafana, Prometheus, Loki, SNMP Exporter, and Grafana Alloy. It also details how to onboard various homelab devices into the monitoring ecosystem.
Part 1: Core Stack Installation
1. LXC Preparation
Create a new Debian LXC in Proxmox and update the system:
2. Install Docker
Install the Docker engine and its dependencies.
Docker Official Docs
# Add Docker's official GPG key:
apt update
apt install ca-certificates curl
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
# Add the repository to Apt sources:
tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/debian
Suites: $(. /etc/os-release && echo "$VERSION_CODENAME")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF
apt update
apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
3. Folder Structure & Permissions
Create the required directory tree for persistent data and configurations:
monitoring/
├── docker-compose.yml
├── grafana/
├── alloy/
│ └── config.alloy
├── prometheus/
│ ├── data/
│ └── prometheus.yml
└── loki/
├── data/
└── loki-config.yaml
Set the proper ownership permissions so the containers can write to their respective directories:
4. Configuration Files
Populate the configuration files within your new directory structure.
prometheus/prometheus.yml
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['prometheus:9090']
- job_name: 'snmp_exporter'
static_configs:
- targets: ['snmp-exporter:9116']
loki/loki-config.yaml
auth_enabled: false
server:
http_listen_port: 3100
common:
path_prefix: /loki
storage:
filesystem:
chunks_directory: /loki/chunks
rules_directory: /loki/rules
replication_factor: 1
ring:
kvstore:
store: inmemory
schema_config:
configs:
- from: 2026-01-01
store: tsdb
object_store: filesystem
schema: v13
index:
prefix: index_
period: 24h
alloy/config.alloy
loki.source.syslog "network_syslog" {
listener {
address = "0.0.0.0:1514"
protocol = "udp"
}
relabel_rules = loki.relabel.syslog_tags.rules
forward_to = [loki.write.local_loki.receiver]
}
loki.relabel "syslog_tags" {
forward_to = []
rule {
source_labels = ["__syslog_message_hostname"]
target_label = "host"
}
rule {
source_labels = ["__syslog_message_severity"]
target_label = "level"
}
rule {
source_labels = ["__syslog_connection_ip_address"]
target_label = "source_ip"
}
rule {
target_label = "job"
replacement = "syslog"
}
}
loki.write "local_loki" {
endpoint {
url = "http://loki:3100/loki/api/v1/push"
}
}
5. Docker Compose Deployment
Create the docker-compose.yml file to tie the stack together:
services:
grafana:
image: grafana/grafana:latest
container_name: grafana
restart: unless-stopped
ports:
- "3000:3000"
volumes:
- ./grafana:/var/lib/grafana
depends_on:
- prometheus
- loki
prometheus:
image: prom/prometheus:latest
container_name: prometheus
restart: unless-stopped
ports:
- "9090:9090"
volumes:
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- ./prometheus/data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--storage.tsdb.retention.time=30d'
- '--web.enable-remote-write-receiver'
loki:
image: grafana/loki:latest
container_name: loki
restart: unless-stopped
ports:
- "3100:3100"
volumes:
- ./loki/loki-config.yaml:/etc/loki/local-config.yaml:ro
- ./loki/data:/loki
command: -config.file=/etc/loki/local-config.yaml
snmp-exporter:
image: prom/snmp-exporter:latest
container_name: snmp-exporter
restart: unless-stopped
ports:
- "9116:9116"
syslog-receiver:
image: grafana/alloy:latest
container_name: syslog-receiver
restart: unless-stopped
ports:
- "1514:1514/udp"
volumes:
- ./alloy/config.alloy:/etc/alloy/config.alloy:ro
command:
- run
- --server.http.listen-addr=0.0.0.0:12345
- /etc/alloy/config.alloy
6. Start the Stack & Initialize Grafana
Start the containers in detached mode:
- Navigate to
http://<YOUR_MONITORING_STACK_IP>:3000to access Grafana. - Log in with the default credentials (
admin/admin). - Go to Connections > Data Sources.
- Add Prometheus using the internal Docker URL:
http://prometheus:9090. - Add Loki using the internal Docker URL:
http://loki:3100.
Part 2: Adding Devices
1. Proxmox VE
Alloy & Node Metrics
SSH into your Proxmox Host (or open the node's web shell in the Proxmox GUI) to install Alloy and hardware sensors:
mkdir -p /etc/apt/keyrings
wget -O /etc/apt/keyrings/grafana.asc https://apt.grafana.com/gpg-full.key
chmod 644 /etc/apt/keyrings/grafana.asc
echo "deb [signed-by=/etc/apt/keyrings/grafana.asc] https://apt.grafana.com stable main" | tee /etc/apt/sources.list.d/grafana.list
apt-get update
apt-get install alloy lm-sensors -y
usermod -aG adm,systemd-journal alloy
Edit the local Alloy config (nano /etc/alloy/config.alloy) and insert:
prometheus.exporter.unix "proxmox" { }
prometheus.scrape "scrape_proxmox" {
targets = prometheus.exporter.unix.proxmox.targets
scrape_interval = "15s"
forward_to = [prometheus.relabel.node_exporter_labels.receiver]
}
prometheus.relabel "node_exporter_labels" {
forward_to = [prometheus.remote_write.local_prometheus.receiver]
rule {
target_label = "job"
replacement = "node-exporter"
}
rule {
target_label = "instance"
replacement = constants.hostname
}
}
prometheus.remote_write "local_prometheus" {
endpoint {
url = "http://<YOUR_MONITORING_STACK_IP>:9090/api/v1/write"
}
}
loki.source.journal "proxmox_journal" {
forward_to = [loki.relabel.proxmox_tags.receiver]
labels = {
job = "proxmox-logs",
host = constants.hostname,
}
}
loki.relabel "proxmox_tags" {
forward_to = [loki.write.local_loki.receiver]
rule {
source_labels = ["__journal__systemd_unit"]
target_label = "unit"
}
rule {
source_labels = ["__journal__transport"]
regex = "kernel"
target_label = "unit"
replacement = "kernel"
}
rule {
source_labels = ["__journal_priority"]
regex = "0|1|2|3"
target_label = "level"
replacement = "error"
}
rule {
source_labels = ["__journal_priority"]
regex = "4"
target_label = "level"
replacement = "warn"
}
rule {
source_labels = ["__journal_priority"]
regex = "5|6"
target_label = "level"
replacement = "info"
}
rule {
source_labels = ["__journal_priority"]
regex = "7"
target_label = "level"
replacement = "debug"
}
rule {
source_labels = ["level"]
regex = "^$"
target_label = "level"
replacement = "info"
}
}
loki.write "local_loki" {
endpoint {
url = "http://<YOUR_MONITORING_STACK_IP>:3100/loki/api/v1/push"
}
}
Enable and start Alloy:
Proxmox API Exporter & S.M.A.R.T
- Log into your Proxmox Web GUI.
- Go to Datacenter > Permissions > Users. Add a user:
pve-exporter, Realm:Proxmox VE authentication server (pve). - Go to Datacenter > Permissions. Add a User Permission: Path:
/, User:pve-exporter@pve, Role:PVEAuditor. - Go to Datacenter > Permissions > API Tokens. Add a token for the user, Token ID:
monitoring, and uncheck Privilege Separation. Copy the Secret value.
Back in the Proxmox SSH shell, set up the exporter daemon:
# Create User
useradd --system --no-create-home --shell /usr/sbin/nologin pve-exporter
# Install Binary
wget -O /usr/local/bin/pve-exporter https://github.com/bigtcze/pve-exporter/releases/latest/download/pve-exporter-linux-amd64
chmod +x /usr/local/bin/pve-exporter
# Create Configuration
mkdir -p /etc/pve-exporter
cat > /etc/pve-exporter/config.yml << 'EOF'
proxmox:
host: "<YOUR_PROXMOX_IP>"
port: 8006
token_id: "pve-exporter@pve!monitoring"
token_secret: "<PASTE_YOUR_SECRET_HERE>"
insecure_skip_verify: true
server:
listen_address: ":9221"
metrics_path: "/metrics"
EOF
chown root:pve-exporter /etc/pve-exporter/config.yml
chmod 640 /etc/pve-exporter/config.yml
# Create Systemd Service
cat > /etc/systemd/system/pve-exporter.service << 'EOF'
[Unit]
Description=Proxmox VE Exporter for Prometheus
Documentation=https://github.com/bigtcze/pve-exporter
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=pve-exporter
Group=pve-exporter
ExecStart=/usr/local/bin/pve-exporter -config /etc/pve-exporter/config.yml
Restart=on-failure
RestartSec=5
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
ReadOnlyPaths=/
ReadWritePaths=
[Install]
WantedBy=multi-user.target
EOF
# Enable and Start
systemctl daemon-reload
systemctl enable --now pve-exporter
# Add S.M.A.R.T Monitoring Script
wget -O /usr/local/bin/pve-smart-collector.sh https://raw.githubusercontent.com/bigtcze/pve-exporter/main/scripts/pve-smart-collector.sh
chmod +x /usr/local/bin/pve-smart-collector.sh
mkdir -p /var/lib/pve-exporter
echo '*/5 * * * * root /usr/local/bin/pve-smart-collector.sh' | tee /etc/cron.d/pve-smart-collector
/usr/local/bin/pve-smart-collector.sh
Prometheus Config Update:
Add this to your prometheus.yml on the monitoring LXC and restart the Prometheus container:
- Grafana Dashboard ID:
24550
2. TrueNAS SCALE
TrueNAS API Exporter
Syslog Forwarding
- Go to System Settings > Advanced.
- Scroll down to the Syslog widget and click Configure.
- Select Syslog level: Info.
- In the Syslog Server field, enter
<YOUR_MONITORING_STACK_IP>:1514. - Set the transport protocol to UDP.
- Check the Audit box.
Deep Metrics via API
The exporter requires a dedicated API key to securely read data from the NAS.
- Navigate to Credentials > Users and add a user (e.g.,
api-readonly) with a strong password. - Navigate to Credentials > Local Groups, edit the newly created
api-readonlygroup, and add the Read-Only Administrator privilege. - Click the Profile/User Icon in the top right corner and select My API Keys.
- Click Add API Key, name it, and ensure the username is set to
api-readonly.
Add the exporter to your monitoring LXC's docker-compose.yml:
truenas-exporter:
image: ghcr.io/unknowlars/truenas-scale-api-prometheus-exporter:latest
container_name: truenas-exporter
restart: unless-stopped
ports:
- "9108:9108"
environment:
- TRUENAS_WS_URL=wss://<YOUR_TRUENAS_IP>/api/current
- TRUENAS_API_KEY=<YOUR_API_KEY>
- TRUENAS_VERIFY_TLS=false
Prometheus Config Update:
Add this to prometheus.yml and restart Prometheus:
- Grafana Dashboard ID:
25081
3. OPNsense Firewall
OPNsense API Exporter
Syslog Forwarding
- Log into your OPNsense Web GUI.
- Navigate to System > Settings > Logging > Remote.
- Click + Add.
- Configure as follows:
- Transport:
UDP (IPv4) - Hostname:
<YOUR_MONITORING_STACK_IP> - Port:
1514 - RFC5424: Checked (✅)
- Description:
Grafana Loki
Basic Node Metrics
- Go to System > Firmware > Plugins.
- Search for and install
os-node_exporter. - Navigate to Services > Prometheus Exporter.
- Check all available boxes and apply.
Deep API Metrics
- Go to System > Access > Users.
- Create a dedicated user (e.g.,
prometheus-scraper). Assign required API privileges as listed in the exporter documentation. - Create an API key for this user. Download the
.txtfile containing the API Key and API Secret.
Add the exporter to your monitoring LXC's docker-compose.yml:
opnsense-exporter:
image: ghcr.io/athennamind/opnsense-exporter:latest
container_name: opnsense-exporter
restart: unless-stopped
ports:
- "8080:8080"
command:
- "--opnsense.protocol=https"
- "--opnsense.address=<YOUR_OPNSENSE_IP>"
- "--exporter.instance-label=opnsense-main"
- "--opnsense.insecure"
- "--exporter.disable-unbound"
environment:
OPNSENSE_EXPORTER_OPS_API_KEY: "<YOUR_API_KEY>"
OPNSENSE_EXPORTER_OPS_API_SECRET: "<YOUR_API_SECRET>"
Prometheus Config Update:
Add this to prometheus.yml and restart Prometheus:
- job_name: 'opnsense-main'
static_configs:
- targets: ['<YOUR_OPNSENSE_IP>:9100']
- job_name: 'opnsense-main-api'
static_configs:
- targets: ['opnsense-exporter:8080']
- Grafana Dashboard IDs:
21113,22248
4. Windows PC
- Open PowerShell as Administrator.
- Install Grafana Alloy via winget:
- Open the configuration file:
- Replace the contents with:
prometheus.exporter.windows "local_system" {
enabled_collectors = ["cpu", "logical_disk", "net", "os", "system", "memory"]
}
prometheus.scrape "scrape_windows" {
targets = prometheus.exporter.windows.local_system.targets
scrape_interval = "15s"
forward_to = [prometheus.remote_write.local_prometheus.receiver]
}
loki.source.windowsevent "application" {
eventlog_name = "Application"
forward_to = [loki.write.local_loki.receiver]
labels = { job = "windows-events", host = "Windows-PC" }
}
loki.source.windowsevent "system" {
eventlog_name = "System"
forward_to = [loki.write.local_loki.receiver]
labels = { job = "windows-events", host = "Windows-PC" }
}
loki.source.windowsevent "security" {
eventlog_name = "Security"
forward_to = [loki.write.local_loki.receiver]
labels = { job = "windows-events", host = "Windows-PC" }
}
prometheus.remote_write "local_prometheus" {
endpoint {
url = "http://<YOUR_MONITORING_STACK_IP>:9090/api/v1/write"
}
}
loki.write "local_loki" {
endpoint {
url = "http://<YOUR_MONITORING_STACK_IP>:3100/loki/api/v1/push"
}
}
- Restart the service to apply changes:
(Verify it shows a "Running" status).
- Grafana Dashboard ID:
16523
5. SNMP Devices (e.g., TP-Link Switch)
SNMP v1/v2c Setup
In your managed switch's web interface (under SNMP config):
- Click + Add.
- Community Name: Type
public. - Access Mode: Select Read-Only.
- MIB View: Leave as default.
- Apply/Save.
Remote Logs Setup (Syslog)
In your switch's Log Server config:
- Check the box for an active Index.
- Server IP:
<YOUR_MONITORING_STACK_IP> - UDP Port:
1514 - Severity: Set to
level_6orlevel_7to capture standard connection logs. - Status: Enable.
Prometheus Config Update:
Add this to prometheus.yml and restart Prometheus:
- job_name: 'snmp'
static_configs:
- targets:
- <YOUR_DEVICE_IP>
metrics_path: /snmp
params:
auth: [public_v2]
module: [if_mib]
relabel_configs:
- source_labels: [__address__]
target_label: __param_target
- source_labels: [__param_target]
target_label: instance
- target_label: __address__
replacement: snmp-exporter:9116
- Grafana Dashboard ID:
11169