Grafana + InfluxDB IoT Monitoring: Complete Production Setup Guide
For industrial IoT monitoring, Grafana + InfluxDB is the reference stack. InfluxDB stores time-series telemetry with automatic retention and fast aggregations. Grafana visualizes it with 50+ panel types, threshold alerting, and multi-team access control — all without writing a single line of frontend code.
This guide takes you from a blank server to a production monitoring stack with MQTT ingestion, Flux queries, alerting, and secure team access.
Architecture
IoT Devices → MQTT Broker (Mosquitto / AWS IoT)
↓ Telegraf (MQTT input plugin)
InfluxDB 2.x (time-series storage)
↓ Flux queries
Grafana (dashboards + alerting)
↓
PagerDuty / Slack / Email (alerts)
Step 1: Docker Compose Stack
docker-compose.yml
version: '3.8'services:
mosquitto:
image: eclipse-mosquitto:2
ports:
- "1883:1883"
- "8883:8883"
volumes:
- ./mosquitto/config:/mosquitto/config
- ./mosquitto/data:/mosquitto/data
influxdb:
image: influxdb:2.7
ports:
- "8086:8086"
volumes:
- influxdb_data:/var/lib/influxdb2
environment:
DOCKER_INFLUXDB_INIT_MODE: setup
DOCKER_INFLUXDB_INIT_USERNAME: admin
DOCKER_INFLUXDB_INIT_PASSWORD: ${INFLUX_PASSWORD}
DOCKER_INFLUXDB_INIT_ORG: codecaracal
DOCKER_INFLUXDB_INIT_BUCKET: iot_telemetry
DOCKER_INFLUXDB_INIT_RETENTION: 30d
telegraf:
image: telegraf:1.31
volumes:
- ./telegraf/telegraf.conf:/etc/telegraf/telegraf.conf:ro
environment:
INFLUX_TOKEN: ${INFLUX_TOKEN}
depends_on:
- influxdb
- mosquitto
grafana:
image: grafana/grafana:10.4.0
ports:
- "3000:3000"
volumes:
- grafana_data:/var/lib/grafana
- ./grafana/provisioning:/etc/grafana/provisioning
environment:
GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_PASSWORD}
GF_AUTH_ANONYMOUS_ENABLED: false
GF_SMTP_ENABLED: true
GF_SMTP_HOST: smtp.sendgrid.net:587
GF_SMTP_USER: apikey
GF_SMTP_PASSWORD: ${SENDGRID_KEY}
depends_on:
- influxdb
volumes:
influxdb_data:
grafana_data:
Step 2: Telegraf MQTT Input
telegraf/telegraf.conf
[agent]
interval = "10s"
flush_interval = "10s"
[[inputs.mqtt_consumer]]
servers = ["tcp://mosquitto:1883"]
topics = ["devices/+/telemetry"]
qos = 1
data_format = "json"
# Extract device ID from topic (devices/{deviceId}/telemetry)
[[inputs.mqtt_consumer.topic_parsing]]
topic = "devices/+/telemetry"
tags = "_/device_id/_"
fields = ""
# Tag each measurement with the device ID
[inputs.mqtt_consumer.tags]
measurement = "device_telemetry"
[[outputs.influxdb_v2]]
urls = ["http://influxdb:8086"]
token = "${INFLUX_TOKEN}"
organization = "codecaracal"
bucket = "iot_telemetry"
Telegraf reads MQTT messages, parses JSON payloads, extracts the device_id from the topic, and writes tagged measurements to InfluxDB. No code required.
Step 3: Flux Queries for Grafana
Flux is InfluxDB's query language. Key queries for IoT dashboards:
Current value (for stat panels / gauges):
from(bucket: "iot_telemetry")
|> range(start: -1h)
|> filter(fn: (r) => r._measurement == "device_telemetry")
|> filter(fn: (r) => r._field == "temperature")
|> filter(fn: (r) => r.device_id == v.device_id) // Dashboard variable
|> last()
Time-series for a chart:
from(bucket: "iot_telemetry")
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r._measurement == "device_telemetry")
|> filter(fn: (r) => r._field == "temperature" or r._field == "humidity")
|> filter(fn: (r) => r.device_id =~ /${device_id:regex}/)
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|> pivot(rowKey: ["_time"], columnKey: ["_field"], valueColumn: "_value")
Fleet overview (all devices, latest value):
from(bucket: "iot_telemetry")
|> range(start: -5m)
|> filter(fn: (r) => r._measurement == "device_telemetry")
|> filter(fn: (r) => r._field == "temperature")
|> group(columns: ["device_id"])
|> last()
|> group()
|> sort(columns: ["device_id"])
Step 4: Grafana Dashboard Variables
Create a device_id variable to make dashboards dynamic:
import "influxdata/influxdb/schema"
schema.tagValues(bucket: "iot_telemetry", tag: "device_id")
This creates a dropdown at the top of the dashboard — select one device or all, and every panel updates automatically.
Step 5: Alerting Rules
Grafana's unified alerting fires on Flux query results:
grafana/provisioning/alerting/rules.yaml
apiVersion: 1
groups:
- orgId: 1
name: IoT Alerts
interval: 1m
rules:
- uid: temperature-high
title: Temperature Above Threshold
condition: C
data:
- refId: A
datasourceUid: influxdb
model:
query: |
from(bucket: "iot_telemetry")
|> range(start: -5m)
|> filter(fn: (r) => r._field == "temperature")
|> last()
- refId: B
datasourceUid: "-100"
model:
type: reduce
reducer: last
- refId: C
datasourceUid: "-100"
model:
type: threshold
conditions:
- evaluator: { type: "gt", params: [80] }
for: 2m
labels:
severity: critical
annotations:
summary: "{{ $labels.device_id }} temperature {{ $values.B.Value }}°C"
Step 6: Panel Types for IoT
| Measurement | Grafana Panel | Config | |-------------|---------------|--------| | Live temperature | Time series | Line, 1h window | | Current value | Stat | Single color threshold | | Battery level | Gauge | 0–100%, red below 20% | | Device online/offline | State timeline | green/red | | Fleet overview | Table | All devices, last value | | Geolocation | Geomap | Lat/lon tags from devices |
Role-Based Access Control
grafana/provisioning/access-control/roles.yaml
apiVersion: 1
roles:
- name: IoT Operator
global: true
permissions:
- action: dashboards:read
scope: "dashboards:*"
- action: alert.instances:read
scope: "global:*"
Clients get Viewer access — they can see dashboards and receive alerts but cannot edit. Your team gets Editor. Only platform admins get Admin.
The Grafana + InfluxDB stack handles 10,000+ data points per second on modest hardware ($200/month AWS) and scales further with InfluxDB clustering. [Contact Code Caracal](/contact) to set this up for your IoT fleet.