Introduction
Grafana is the go-to tool for visualizing real-time IoT data, especially with time-series sources like InfluxDB and protocols like MQTT. In 2026, with the explosion of edge sensors (over 75 billion projected), an expert Grafana implementation enables horizontal scalability, interactive dashboards, and proactive alerts on critical metrics like temperature, humidity, or energy consumption.
This expert tutorial guides you step-by-step to deploy a complete stack: Grafana + InfluxDB + Mosquitto MQTT + Telegraf. We use provisioning for declarative configuration, ideal for production Kubernetes or Docker Swarm. Each step includes working code, advanced Flux queries, and optimizations for 10k+ data points per second. By the end, you'll have a system monitoring simulated IoT data, ready to scale to real fleets. Why it matters: Poor IoT visualization costs 20-30% in downtime; Grafana cuts that with dynamic panels and ML-based anomaly detection.
Prerequisites
- Docker and Docker Compose v2.20+
- Advanced knowledge of InfluxDB 2.x (Flux/QL), MQTT v5, and YAML provisioning
- Linux server with 4GB RAM minimum (16GB+ for production)
- Tools:
mosquitto_pubfor MQTT testing, YAML/JSON editor - Grafana admin access (default: admin/admin)
Deploy the Docker Compose Stack
version: '3.8'
services:
influxdb:
image: influxdb:2.7
container_name: influxdb
ports:
- '8086:8086'
environment:
- DOCKER_INFLUXDB_INIT_MODE=setup
- DOCKER_INFLUXDB_INIT_USERNAME=admin
- DOCKER_INFLUXDB_INIT_PASSWORD=adminpass
- DOCKER_INFLUXDB_INIT_ORG=iot
- DOCKER_INFLUXDB_INIT_BUCKET=iot_bucket
- DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=token_influx
volumes:
- influxdb_data:/var/lib/influxdb2
restart: unless-stopped
grafana:
image: grafana/grafana:11.0.0
container_name: grafana
ports:
- '3000:3000'
environment:
- GF_SECURITY_ADMIN_USER=admin
- GF_SECURITY_ADMIN_PASSWORD=admin
- GF_USERS_ALLOW_SIGN_UP=false
volumes:
- grafana_data:/var/lib/grafana
- ./grafana/provisioning:/etc/grafana/provisioning
depends_on:
- influxdb
restart: unless-stopped
mosquitto:
image: eclipse-mosquitto:2.0
container_name: mosquitto
ports:
- '1883:1883'
volumes:
- ./mosquitto.conf:/mosquitto/config/mosquitto.conf
restart: unless-stopped
telegraf:
image: telegraf:1.28
container_name: telegraf
volumes:
- ./telegraf.conf:/etc/telegraf/telegraf.conf:ro
depends_on:
- influxdb
- mosquitto
restart: unless-stopped
volumes:
influxdb_data:
grafana_data:This docker-compose.yml deploys a complete stack: InfluxDB for storing IoT time-series, Grafana for visualization, Mosquitto as the MQTT broker, and Telegraf to ingest MQTT data into InfluxDB. Volumes persist data; tokens and org are ready for provisioning. Run with docker compose up -d. Pitfall: Check exposed ports to avoid firewall conflicts.
Verify Deployment and Configure MQTT
After docker compose up -d, access Grafana at http://localhost:3000 (admin/admin). InfluxDB UI: http://localhost:8086 (token: token_influx). Create a simple mosquitto.conf file:
plaintext
listener 1883
allow_anonymous true
Test MQTT: mosquitto_pub -h localhost -t iot/sensor/temp -m '25.3'. Telegraf will collect this into InfluxDB. This setup simulates an IoT sensor publishing every 10 seconds.
Provision InfluxDB Datasource
apiVersion: 1
datasources:
- name: InfluxDB-IoT
type: influxdb
access: proxy
orgId: 1
url: http://influxdb:8086
version: Flux
jsonData:
defaultBucket: iot_bucket
organization: iot
tlsSkipVerify: true
secureJsonData:
token: token_influx
isDefault: true
editable: falseThis YAML file automatically provisions the InfluxDB datasource in Grafana on startup. Use Flux for advanced IoT queries; proxy mode caches them. Place it in ./grafana/provisioning/datasources/. Expert advantage: Declarative and reproducible in CI/CD. Pitfall: Token is insecure in dev; use secrets in production via secureJsonData with Vault.
Telegraf Configuration for MQTT IoT
[[inputs.mqtt_consumer]]
servers = ["tcp://mosquitto:1883"]
topics = [
"iot/sensor/#"
]
data_format = "value"
data_type = "float"
[[inputs.mqtt_consumer.tags]]
sensor_type = "${topic}"
[[outputs.influxdb_v2]]
urls = ["http://influxdb:8086"]
token = "token_influx"
organization = "iot"
bucket = "iot_bucket"
[[processors.enum]]
[[processors.enum.mapping]]
field = "value"
destination = "temperature"
[processors.enum.mapping.value_mappings]
"25.3" = "25.3"
[[processors.starlark]]
script = """
def apply(metric):
metric.tags['location'] = 'factory'
return metric
"""Telegraf ingests MQTT topics iot/sensor/* into InfluxDB, parsing float values as the 'temperature' metric. Processors add tags (like location) and mappings for IoT context. Restart Telegraf after editing. Expert tip: Starlark for custom transformations (e.g., geolocation). Pitfall: Wildcard # topics can overload; filter with qos=1 in production.
Create and Test an IoT Dashboard
Publish MQTT data: while true; do mosquitto_pub -h localhost -t iot/sensor/temp -m $((25 + RANDOM % 5)); sleep 10; done. In Grafana > Dashboards > New > Import, use the JSON below. Panels include Graph for trends, Stat for thresholds, and Table for logs. Use Flux queries for windowed aggregations.
Complete IoT Dashboard in JSON
{
"__inputs": [],
"id": null,
"title": "IoT Sensors Dashboard",
"tags": [],
"style": "dark",
"timezone": "browser",
"panels": [
{
"id": 1,
"title": "Température Temps Réel",
"type": "timeseries",
"targets": [
{
"query": "from(bucket: "iot_bucket")
|> range(start: v.timeRangeStart)
|> filter(fn: (r) => r._measurement == "temperature")
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)",
"refId": "A",
"datasource": {
"type": "influxdb",
"uid": "InfluxDB-IoT"
}
}
],
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 0 },
"fieldConfig": {
"defaults": {
"unit": "celsius",
"color": { "mode": "palette-classic" }
}
}
},
{
"id": 2,
"title": "Stat Température Actuelle",
"type": "stat",
"targets": [
{
"query": "from(bucket: "iot_bucket")
|> range(start: -1m)
|> filter(fn: (r) => r._measurement == "temperature")
|> last()",
"refId": "A",
"datasource": { "type": "influxdb", "uid": "InfluxDB-IoT" }
}
],
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 0 },
"fieldConfig": {
"defaults": {
"unit": "celsius",
"thresholds": {
"steps": [
{ "color": "green", "value": null },
{ "color": "yellow", "value": 28 },
{ "color": "red", "value": 30 }
]
}
}
}
}
],
"time": {
"from": "now-1h",
"to": "now"
},
"timepicker": {},
"refresh": "10s",
"schemaVersion": 39,
"version": 1
}This importable JSON dashboard shows a timeseries for temperature trends (mean/window aggregated) and a stat for the current value with color thresholds. 10s refresh for real-time IoT. aggregateWindow handles expert downsampling. Save and provision via /provisioning/dashboards/ for auto-loading. Pitfall: Queries without createEmpty: false clutter empty graphs.
Grafana Alert Rule for IoT
apiVersion: 1
groups:
- orgId: 1
name: IoT Alerts
folder: IoT
interval: 60s
rules:
- uid: temp-high
title: "Température IoT élevée"
condition: B
data:
- refId: A
queryType: ""
relativeTimeRange:
from: 600
to: 0
datasourceUid: "InfluxDB-IoT"
model:
expr: "from(bucket: \"iot_bucket\")
|> range(start: -10m)
|> filter(fn: (r) => r._measurement == \"temperature\")
|> last()
|> map(fn: (r) => ({ r with value: r._value }))
|> filter(fn: (r) => r.value > 30.0)
refId: B
queryType: ""
datasourceUid: "__expr__"
model:
conditions:
- evaluator:
params: [30]
type: gt
operator:
type: and
query:
params: ["A"]
reducer:
params: []
type: last
type: query
noDataState: NoData
execErrState: Error
for: 1m
annotations:
summary: "Temp > 30°C sur capteur IoT"
labels:
severity: critical
record: temp_high
notifications: []
isPaused: falseThis provisioned YAML rule alerts if the last temperature >30°C for 1m (debounce). Flux last() + filter for IoT precision; notify via email/Slack in production. Place in ./grafana/provisioning/alerting/. Expert tip: for: 1m avoids false positives from noisy data. Pitfall: Without range(-10m), you miss spikes; test in Grafana > Alerting.
Best Practices
- Provisioning everywhere: Keep dashboards/datasources/alerts in Git as YAML/JSON for IaC and zero-downtime rollouts.
- IoT Downsampling: Use InfluxDB Continuous Queries or
aggregateWindow(every:1h)for long-term retention (1 year+). - MQTT Security: Enable TLS/ACLS on Mosquitto; rotate tokens via Vault.
- Scalability: Grafana Enterprise cluster + InfluxDB IOx for 1M+ metrics/s; Loki for IoT logs.
- ML Anomalies: Grafana ML plugin for outlier detection on sensors (e.g., abnormal temperature).
Common Errors to Avoid
- No downsampling: InfluxDB databases explode (>1TB/month) without retention policies; limit
infiniteto 7d max. - Unoptimized queries: Flux without early
filterscans everything; always start with|> filter(fn: (r) => r._measurement == "temp"). - Overly aggressive refresh: 5s+ is fine for IoT, but <1s kills performance; use dashboard variables for dynamism.
- Misconfigured CORS/Proxy:
directdatasource exposes tokens; enforceproxywith NGINX reverse proxy.
Next Steps
Dive deeper with Grafana IoT Plugins (e.g., native MQTT datasource). Migrate to Grafana Cloud for managed IoT. Check the advanced InfluxDB Flux docs.
Explore our Learni DevOps & IoT training courses for Grafana Certified and edge computing stacks.