Back to Blog
IoT Dashboard

Grafana + InfluxDB IoT Monitoring: Complete Production Setup Guide

Grafana and InfluxDB are the industry standard for IoT monitoring dashboards. This guide covers the complete stack from MQTT ingestion through Telegraf to production-ready Grafana panels with alerting, team access control, and Docker deployment.

October 1, 2024
14 min read
GrafanaInfluxDBIoT MonitoringTelegraf

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:

  • 1. Dashboard → Settings → Variables → Add variable
  • 2. Type: Query, Query type: InfluxDB
  • 3. Query:
  • import "influxdata/influxdb/schema"
    schema.tagValues(bucket: "iot_telemetry", tag: "device_id")
    
  • 4. Multi-value: enabled (select all or specific devices)
  • 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.

    ⚡
    Written by CodeCaracal Engineering

    We write from production experience — every technique in our articles has been deployed to real clients. No academic theory.

    Let's Build Together

    Got an IoT challenge?
    We've shipped it.

    Whether you need a fleet to track, a factory to monitor, or a farm to automate — our team has done it before and we'd love to build it with you. Typical response time: under 24 hours.

    No upfront commitment99.9% uptime SLANDA on requestFixed-price options