Introduction
In 2026, Grafana remains the go-to tool for visualizing real-time IoT data, natively integrating MQTT, InfluxDB, and Prometheus for interactive dashboards. This expert tutorial guides you through implementing a production-ready stack: Dockerized deployment, automated provisioning of datasources and dashboards, alerting on sensor anomalies (temperature, humidity, vibrations), and horizontal scaling. Why it matters: IoT fleets generate terabytes of time series data; Grafana excels at aggregating them with Loki queries for logs and geospatial panels for tracking. You'll learn to avoid latency traps in high-frequency data, secure with OAuth, and provision via GitOps. Result: pro monitoring that alerts in <5s on critical thresholds, bookmark-worthy for any senior IoT DevOps engineer. (128 words)
Prerequisites
- Docker 27+ and Docker Compose 2.29+
- Advanced knowledge of YAML provisioning and InfluxDB 3.x
- Access to an MQTT broker (Mosquitto 2.1+) and simulated sensors
- Node.js 22+ for Grafana API tests
- Production Linux system (Ubuntu 24.04 recommended)
Docker Compose Stack Deployment
version: '3.8'
services:
grafana:
image: grafana/grafana-enterprise:11.1.0
container_name: grafana-iot
ports:
- '3000:3000'
volumes:
- grafana-data:/var/lib/grafana
- ./provisioning:/etc/grafana/provisioning
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin123!
- GF_AUTH_ANONYMOUS_ENABLED=false
- GF_SERVER_ROOT_URL=http://localhost:3000
restart: unless-stopped
depends_on:
- influxdb
- mosquitto
influxdb:
image: influxdb:3.0
container_name: influxdb-iot
ports:
- '8086:8086'
volumes:
- influxdb-data:/var/lib/influxdb3
environment:
- DOCKER_INFLUXDB_INIT_MODE=setup
- DOCKER_INFLUXDB_INIT_USERNAME=iot-admin
- DOCKER_INFLUXDB_INIT_PASSWORD=securepass123
- DOCKER_INFLUXDB_INIT_ORG=iot-org
- DOCKER_INFLUXDB_INIT_BUCKET=iot-sensors
- DOCKER_INFLUXDB_INIT_TOKEN=iot-token-abc123
restart: unless-stopped
mosquitto:
image: eclipse-mosquitto:2.0
container_name: mosquitto-iot
ports:
- '1883:1883'
- '9001:9001'
volumes:
- ./mosquitto.conf:/mosquitto/config/mosquitto.conf
restart: unless-stopped
volumes:
grafana-data:
influxdb-data:This docker-compose.yml deploys Grafana Enterprise 11.1, InfluxDB 3 for IoT time series, and Mosquitto MQTT. Volumes persist data; provisioning mounts a folder for automated configs. Run with docker compose up -d. Pitfall: forget depends_on, and InfluxDB won't be ready for the datasource.
Mosquitto Configuration for MQTT
Create mosquitto.conf to enable WebSocket (port 9001) and basic auth, essential for the Grafana MQTT plugin. This enables real-time subscriptions without polling, reducing latency to <100ms for high-frequency sensor data.
Secure Mosquitto MQTT Config
persistence true
persistence_location /mosquitto/data/
log_dest file /mosquitto/log/mosquitto.log
listener 1883
listener 9001
protocol websockets
allow_anonymous true
max_queued_messages 10000
message_size_limit 0
max_inflight_messages 1000
Enables persistence and WebSockets for Grafana. max_queued_messages handles IoT bursts (e.g., 10k events/s from a fleet). In production, set allow_anonymous false with users/password. Test with mosquitto_sub -h localhost -t iot/sensor/#.
Provisioning Grafana Datasources
Use YAML provisioning for GitOps: datasources auto-created on boot. InfluxDB for historical metrics, MQTT for live streams. Analogy: like Terraform for infra, but for Grafana UI.
InfluxDB Datasource Provisioning
apiVersion: 1
providers:
- name: 'iot-influxdb'
orgId: 1
folder: ''
type: influxdb
uid: influxdb-iot
url: http://influxdb:8086
access: proxy
database: iot-sensors
user: iot-admin
secureJsonData:
password: 'securepass123'
jsonData:
version: Flux
organization: iot-org
defaultBucket: iot-sensors
tlsSkipVerify: true
editable: false
Configures InfluxDB 3.x with Flux queries for IoT time series. proxy mode caches queries; editable: false prevents UI changes. Set editable: true in dev. Check Grafana logs for 'datasource added'.
MQTT Datasource Provisioning
apiVersion: 1
providers:
- name: 'iot-mqtt'
orgId: 1
folder: ''
type: mqtt
uid: mqtt-iot
url: ws://mosquitto:9001
access: proxy
jsonData:
sharedTopic: false
keepAlive: 60
secureJsonData:
user: iot-user
password: mqttpass
editable: false
Grafana's native MQTT plugin for live subscriptions (topic iot/sensor/+). WebSocket URL for browser compatibility. keepAlive 60s handles flaky IoT reconnections. Install plugin with grafana-cli plugins install grafana-mqtt-datasource if not Enterprise.
Real-Time IoT Dashboard
Import an expert JSON dashboard with variables (device ID), gauge/stat panels for sensors, heatmap for trends. Flux queries for aggregations (mean temp per hour).
IoT Sensors Dashboard JSON
{
"__inputs": [],
"id": null,
"title": "IoT Sensors Dashboard",
"tags": [],
"style": "dark",
"timezone": "browser",
"panels": [
{
"id": 1,
"title": "Température Live",
"type": "timeseries",
"targets": [
{
"refId": "A",
"datasource": {
"type": "mqtt",
"uid": "mqtt-iot"
},
"topic": "iot/sensor/$device/temp",
"measurements": ["value"]
}
],
"fieldConfig": {
"defaults": {
"unit": "celsius",
"min": -20,
"max": 80
}
},
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 0 }
},
{
"id": 2,
"title": "Historique Temp/Humidité",
"type": "timeseries",
"targets": [
{
"refId": "A",
"datasource": {
"type": "influxdb",
"uid": "influxdb-iot"
},
"query": "from(bucket: "iot-sensors")
|> range(start: v.timeRangeStart)
|> filter(fn: (r) => r._measurement == "sensor")
|> filter(fn: (r) => r._field == "temp" or r._field == "humidity")
|> aggregateWindow(every: v.windowPeriod, fn: mean)",
"format": "time_series"
}
],
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 0 }
}
],
"time": {
"from": "now-1h",
"to": "now"
},
"timepicker": {},
"variables": [
{
"name": "device",
"label": "Device ID",
"type": "custom",
"options": [
{ "text": "sensor1", "value": "sensor1" },
{ "text": "sensor2", "value": "sensor2" }
],
"current": { "selected": true, "text": "sensor1", "value": "sensor1" }
}
],
"refresh": "10s",
"schemaVersion": 39,
"version": 1,
"revision": 1
}Complete dashboard with MQTT live panel and InfluxDB Flux query for history. Variable $device for multi-sensors. 10s refresh for near-real-time; auto-scaling Celsius units. Provision via dashboards/ folder and restart Grafana.
MQTT Publisher Test Script
Simulate sensor data to validate: Python script publishes random temp/humidity to topics.
Python IoT Data Publisher
import paho.mqtt.client as mqtt
import json
import random
import time
BROKER = "localhost"
PORT = 1883
TOPICS = ["iot/sensor/sensor1/temp", "iot/sensor/sensor1/humidity"]
client = mqtt.Client()
client.connect(BROKER, PORT, 60)
while True:
for topic in TOPICS:
field = topic.split('/')[-1]
if field == 'temp':
value = round(random.uniform(20, 35), 2)
else:
value = round(random.uniform(40, 70), 2)
payload = json.dumps({"device": "sensor1", "value": value, "ts": time.time()})
client.publish(topic, payload)
print(f"Published {topic}: {payload}")
time.sleep(5)
client.loop_forever()Uses Paho MQTT for IoT-like data bursts. InfluxDB can sink via Telegraf (add it). json.dumps for structured payloads; 5s loop simulates 12 samples/min. Run pip install paho-mqtt then python iot_publisher.py.
Alerting Rules Provisioning
apiVersion: 1
groups:
- orgId: 1
name: IoT Alerts
folder: IoT
interval: 30s
rules:
- uid: temp-high
title: "Température capteur élevée"
condition: "B"
data:
- refId: A
datasourceUid: influxdb-iot
model: flux
relativeTimeRange:
from: 600
to: 0
queryText: |
from(bucket: "iot-sensors")
|> range(start: -10m)
|> filter(fn: (r) => r._measurement == "sensor")
|> filter(fn: (r) => r._field == "temp")
|> last()
- refId: B
conditions:
- evaluator:
params: [35]
operator:
type: gt
reducer:
type: last
type: query
noDataState: NoData
execErrState: Error
for: 1m
annotations:
summary: "Temp > 35°C sur {{ $labels.device }}"
labels:
severity: critical
Rule alerts if last temp >35°C for 1m. Flux last() for current value; Jinja annotations for Slack/Teams notifications. for: 1m prevents flapping. Link to contact points via UI after provisioning.
Best Practices
- GitOps Provisioning: Store YAML/JSON in Git, sync with grafana-toolbox for CI/CD.
- Horizontal Scaling: Deploy Grafana in HA cluster (multi-replicas + shared PostgreSQL DB).
- IoT Security: Enable mTLS on MQTT, OAuth2 for datasources, and rate-limit Grafana API.
- Query Optimization: Use InfluxDB downsampling (e.g., 1s→1m) for legacy dashboards.
- Backups: Cron
grafana-cli admin reset-admin-password+ S3 for dashboard snapshots.
Common Errors to Avoid
- Datasource Timing: InfluxDB not ready → wait 30s post-boot or add Docker healthchecks.
- MQTT Reconnects: Without
keepAlive, reconnections fail on flaky IoT networks; set 60s+. - Dashboard Refresh: <5s kills perf (100% CPU); cap at 10s + MQTT WebSockets.
- Alert Flapping: Forget
for: 0→ notification spam; always set min duration.
Next Steps
Master Grafana Loki for IoT logs or Prometheus + Node Exporter for edge metrics. Check out Learni DevOps & IoT training for advanced certifications. Official docs: Grafana IoT docs. GitHub repo example: clone this tutorial to fork.