Skip to content
Learni
View all tutorials
Monitoring

How to Deploy Grafana for IoT in 2026

Lire en français

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

docker-compose.yml
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

mosquitto.conf
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 0

Enables 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

telegraf.conf
[[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

provisioning/datasources/influxdb.yml
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

provisioning/dashboards/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

iot_publisher.py
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

start.sh
#!/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

Check out our Learni training on Monitoring & IoT for hands-on masterclasses.