Introduction
In 2026, Grafana remains the go-to tool for real-time IoT data visualization, thanks to its flexibility and advanced plugins. This advanced tutorial guides you through deploying a complete stack: MQTT broker (Mosquitto), Telegraf collector to InfluxDB, and Grafana with automatic provisioning. Imagine monitoring industrial sensors—temperature, humidity, vibrations—with interactive dashboards, unified alerts, and horizontal scaling.
Why is this crucial? IoT fleets generate terabytes of data; poor visualization leads to costly downtimes. We cover everything from Docker deployment to Flux query optimization, including real-data simulations. By the end, you'll have a production-ready, scalable architecture for 1000+ devices. Ready to turn your IoT metrics into actionable insights? (128 words)
Prerequisites
- Docker and Docker Compose 20+ installed
- Advanced knowledge of YAML, TOML, and Flux (InfluxDB 2.x query language)
- Python 3.10+ for simulator scripts
- Ports 3000 (Grafana), 8086 (InfluxDB), 1883 (MQTT) free
- Minimum 4 GB RAM for local stack
Deploy the Docker Compose Stack
version: '3.8'
services:
mosquitto:
image: eclipse-mosquitto:2.0
ports:
- '1883:1883'
volumes:
- ./mosquitto.conf:/mosquitto/config/mosquitto.conf
influxdb:
image: influxdb:2.7
ports:
- '8086:8086'
environment:
DOCKER_INFLUXDB_INIT_MODE: setup
DOCKER_INFLUXDB_INIT_USERNAME: admin
DOCKER_INFLUXDB_INIT_PASSWORD: adminpass
DOCKER_INFLUXDB_INIT_ORG: iot-org
DOCKER_INFLUXDB_INIT_BUCKET: iot-bucket
DOCKER_INFLUXDB_INIT_ADMIN_TOKEN: token123
volumes:
- influxdb_data:/var/lib/influxdb2
telegraf:
image: telegraf:1.28
depends_on:
- influxdb
- mosquitto
volumes:
- ./telegraf.conf:/etc/telegraf/telegraf.conf:ro
environment:
INFLUX_TOKEN: token123
grafana:
image: grafana/grafana:10.4.0
ports:
- '3000:3000'
depends_on:
- influxdb
volumes:
- grafana_data:/var/lib/grafana
- ./provisioning:/etc/grafana/provisioning
volumes:
influxdb_data:
grafana_data:This docker-compose.yml orchestrates the 4 essential services: Mosquitto for MQTT, InfluxDB 2.x for storage, Telegraf for ingestion, and Grafana for visualization. Volumes persist data; depends_on ordering ensures sequential startup. Pitfall: Without :ro on configs, containers overwrite your local files.
Configure the MQTT Broker
Mosquitto acts as the central hub for your IoT topics (e.g., sensors/temp/room1). Create the config file to enable persistence and basic auth—scalable for 10k+ connections.
Mosquitto Configuration
persistence true
persistence_location /mosquitto/data/
log_dest file /mosquitto/log/mosquitto.log
listener 1883
allow_anonymous true
max_queued_messages 10000
message_size_limit 0
max_inflight_messages 100
max_queued_bytes 0Enables persistence to retain QoS1/2 messages after restarts. High limits handle IoT bursts; allow_anonymous for dev, switch to ACL+users in production. Pitfall: Without max_queued, a flooding publisher exhausts RAM.
Ingestion with Telegraf
Telegraf reads from MQTT and writes to InfluxDB using Flux. Configure inputs/outputs for optimized batching—reduces latency by 50% on high throughput.
Telegraf Configuration
[[outputs.influxdb_v2]]
urls = ["http://influxdb:8086"]
token = "token123"
organization = "iot-org"
bucket = "iot-bucket"
[[inputs.mqtt_consumer]]
servers = ["tcp://mosquitto:1883"]
topics = [
"sensors/+/temp",
"sensors/+/humidity"
]
data_format = "value"
data_type = "float"
[[inputs.mqtt_consumer.tags]]
device_id = "${topic:sensors/}"
[[processors.enum]]
[[processors.enum.mapping]]
field = "topic"
[processors.enum.mapping.value_mappings]
"sensors/room1/temp" = "temperature"
"sensors/room1/humidity" = "humidity"MQTT inputs with wildcards capture all sensors/*; outputs to dedicated bucket. Processors add tags/metadata for Grafana queries. Pitfall: Without data_type=float, Influx infers poorly, breaking graphs.
Grafana Provisioning
Automate datasources and dashboards via YAML/JSON—no manual config, perfect for CI/CD. Create provisioning/ folders.
InfluxDB Datasource Provisioning
apiVersion: 1
providers:
- name: 'iot-influxdb'
orgId: 1
type: influxdb
url: http://influxdb:8086
access: proxy
jsonData:
version: Flux
organization: 'iot-org'
defaultBucket: 'iot-bucket'
tlsSkipVerify: true
secureJsonData:
token: 'token123'YAML provisions Flux-ready datasource. Proxy mode for server-side queries. Pitfall: tlsSkipVerify=true for dev only; in prod, use certs + secureJsonData via secrets.
IoT Dashboard JSON
{
"__inputs": [],
"dashboard": {
"id": null,
"title": "IoT Sensors Dashboard",
"tags": ["iot"],
"panels": [
{
"id": 1,
"title": "Températures par Device",
"type": "timeseries",
"targets": [{
"query": "from(bucket: "iot-bucket")
|> range(start: v.timeRangeStart)
|> filter(fn: (r) => r._measurement == "temp")
|> aggregateWindow(every: v.windowPeriod, fn: mean)",
"refId": "A",
"datasource": "-- Grafana --",
"model": {"dataSource":{"type":"-- Grafana --","uid":"${datasource}"}},
"format": "time_series"
}],
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 0},
"options": {"legend":{"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"multi"}}
},
{
"id": 2,
"title": "Humidité Moyenne",
"type": "stat",
"targets": [{
"query": "from(bucket: "iot-bucket")
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r._measurement == "humidity")
|> mean()",
"refId": "A",
"datasource": "-- Grafana --"
}],
"fieldConfig": {"defaults":{"color":{"mode":"thresholds"},"thresholds":{"steps":[{"color":"green","value":null},{"color":"yellow","value":60},{"color":"red","value":80}]},
"unit": "percent (0-100)"}},
"gridPos": {"h": 8, "w": 12, "x": 12, "y": 0}
}
],
"time": {"from":"now-6h","to":"now"},
"timezone":"browser",
"refresh":"5s",
"schemaVersion": 39,
"version": 1
},
"overwrite": true
}Provisioned JSON dashboard with timeseries/stat panels for temp/humidity. Aggregated Flux queries; 5s refresh for near-real-time. Pitfall: ${datasource} UID must match provisioning; test with Grafana API.
Simulate IoT Data
Run a Python publisher to inject realistic data—simulates 10 sensors with Gaussian noise.
MQTT Publisher Script
import paho.mqtt.client as mqtt
import time
import random
import json
from threading import Thread
client = mqtt.Client()
client.connect("localhost", 1883, 60)
sensors = ["room1", "room2", "factory1"]
def publish_sensor(sensor):
while True:
temp = 22 + random.gauss(0, 2)
hum = 50 + random.gauss(0, 10)
payload = json.dumps({"temp": temp, "humidity": hum, "timestamp": time.time()})
client.publish(f"sensors/{sensor}/temp", temp)
client.publish(f"sensors/{sensor}/humidity", hum)
time.sleep(5)
threads = []
for s in sensors:
t = Thread(target=publish_sensor, args=(s,))
t.start()
threads.append(t)
for t in threads:
t.join()
client.loop_forever()Paho MQTT publishes float values on wildcard-ready topics. Threads per sensor for parallelism; Gaussian for realism. Pitfall: Without connect() retry, reconnection fails; add client.reconnect_delay_set() in production.
Start and Test
Start: docker compose up -d && python iot_publisher.py. Access Grafana at :3000 (admin/admin). Dashboard auto-loads. Live Flux queries on 5s refresh.
Startup Script
#!/bin/bash
docker compose up -d
python3 iot_publisher.py &
grafana-cli plugins install grafana-clock-panel
grafana-cli plugins install vertamedia-clickhouse-datasource
echo "Stack démarrée. Grafana: http://localhost:3000"Bash one-liner for stack + background publisher. Optional plugins for extensions. Pitfall: Without &, publisher blocks; use nohup for detach.
Best Practices
- Provisioning everywhere: YAML/JSON for GitOps, avoid manual UI.
- Optimized Flux queries: Use aggregateWindow(every: 10s) for downsampling, <1% CPU.
- Security: MQTT ACL, Influx RBAC, Grafana OAuth; secrets via Docker Swarm.
- Scaling: Influx IOx cluster, distributed Telegraf agents, Grafana HA.
- Alerting: Unified rules on anomalies (e.g., temp>30°C), notify Slack/Teams.
Common Errors to Avoid
- Malformed MQTT wildcard: "+/+" instead of "sensors/+/temp" loses tags; test with mosquitto_sub.
- Expired Influx token: Regenerate via UI, don't hardcode.
- Dashboard not provisioned: Check Grafana logs (
docker logs grafana) for path errors. - High cardinality: Too many unique tags (device_id per ms) explodes storage; use bucketing + downsample.
Next Steps
- Official docs: Grafana IoT docs
- Advanced plugins: Grafana MQTT plugin
- Production scaling: Kubernetes with Grafana Helm charts.