IoT Tutorials

TIG Stack with Docker Compose for MQTT Data

A complete docker-compose setup for Telegraf, InfluxDB 2 and Grafana that subscribes to an MQTT broker, stores sensor data and opens with a provisioned data source and dashboard, plus the pitfalls that break first runs.

TIG stack Docker Compose diagram with Telegraf reading MQTT topics, InfluxDB storing metrics and Grafana dashboards
Run this on your own MQTT brokerGet a private, managed MQTT broker on the Free plan and start from ready-made code samples.
Try it free Code samples No card required

A TIG stack with Docker Compose gives you a complete MQTT data pipeline on one machine: Telegraf subscribes to your broker, InfluxDB stores the readings and Grafana charts them. In this guide you will create every file needed, start the stack with one command, and open Grafana to a dashboard that is already provisioned and showing live data.

What You Will Build

The stack has three containers and one external dependency, the MQTT broker:

devices / simulator --> MQTT broker (broker.connectmq.com)
                              |
                         Telegraf (mqtt_consumer)
                              |
                         InfluxDB 2 (bucket "sensors")
                              |
                         Grafana (provisioned data source + dashboard)

The broker stays outside Compose on purpose. Brokers are long-lived infrastructure that many consumers share; the TIG stack is one of those consumers. Telegraf connects to the broker as an ordinary MQTT client, which works the same for the public test broker and for a private one.

Why Docker Compose for the TIG Stack

Each of the three tools can be installed natively, but doing so means three package repositories, three service definitions and configuration files scattered across the system. Compose puts the whole pipeline into one folder that you can version, copy to another machine or tear down without leaving traces. It also pins versions explicitly, so an operating system update never upgrades InfluxDB or Grafana behind your back.

The trade-off is that Compose is a single-host tool. It is a good fit for a development laptop, a lab, a small site gateway or an on-premises server that collects data from one plant. It does not give you high availability; if the host goes down, so does the stack. For many monitoring use cases that is acceptable, because devices keep publishing to the broker and Telegraf resumes when it comes back, and if you switch Telegraf to QoS 1 with persistent_session = true, the broker can hold its messages during a short outage.

Project Layout

Create this directory structure:

tig-mqtt/
  .env
  docker-compose.yml
  telegraf/
    telegraf.conf
  grafana/
    provisioning/
      datasources/
        influxdb.yml
      dashboards/
        dashboards.yml
    dashboards/
      mqtt-sensors.json

Six files in total. Each is shown in full below.

Step 1: Environment Variables

Secrets and names live in .env, which Docker Compose reads automatically. Keep it out of version control.

# .env
INFLUX_USERNAME=admin
INFLUX_PASSWORD=change-me-please
INFLUX_ORG=iot
INFLUX_BUCKET=sensors
INFLUX_TOKEN=replace-with-a-long-random-token
GRAFANA_PASSWORD=change-me-too

MQTT_SERVER=tcp://broker.connectmq.com:1883
MQTT_TOPIC_PREFIX=connectmq/demo/a1b2c3
MQTT_CLIENT_ID=telegraf-a1b2c3
MQTT_USERNAME=
MQTT_PASSWORD=

Generate a real token with openssl rand -hex 32. Replace a1b2c3 with your own random string so your topics stay separate on the public broker.

Step 2: The TIG Stack Docker Compose File

# docker-compose.yml
services:
  influxdb:
    image: influxdb:2.7
    restart: unless-stopped
    ports:
      - "8086:8086"
    environment:
      DOCKER_INFLUXDB_INIT_MODE: setup
      DOCKER_INFLUXDB_INIT_USERNAME: ${INFLUX_USERNAME}
      DOCKER_INFLUXDB_INIT_PASSWORD: ${INFLUX_PASSWORD}
      DOCKER_INFLUXDB_INIT_ORG: ${INFLUX_ORG}
      DOCKER_INFLUXDB_INIT_BUCKET: ${INFLUX_BUCKET}
      DOCKER_INFLUXDB_INIT_RETENTION: 30d
      DOCKER_INFLUXDB_INIT_ADMIN_TOKEN: ${INFLUX_TOKEN}
    volumes:
      - influxdb-data:/var/lib/influxdb2
      - influxdb-config:/etc/influxdb2
    healthcheck:
      test: ["CMD", "influx", "ping"]
      interval: 10s
      timeout: 5s
      retries: 12

  telegraf:
    image: telegraf:1.32
    restart: unless-stopped
    depends_on:
      influxdb:
        condition: service_healthy
    environment:
      INFLUX_TOKEN: ${INFLUX_TOKEN}
      INFLUX_ORG: ${INFLUX_ORG}
      INFLUX_BUCKET: ${INFLUX_BUCKET}
      MQTT_SERVER: ${MQTT_SERVER}
      MQTT_TOPIC_PREFIX: ${MQTT_TOPIC_PREFIX}
      MQTT_CLIENT_ID: ${MQTT_CLIENT_ID}
      MQTT_USERNAME: ${MQTT_USERNAME}
      MQTT_PASSWORD: ${MQTT_PASSWORD}
    volumes:
      - ./telegraf/telegraf.conf:/etc/telegraf/telegraf.conf:ro

  grafana:
    image: grafana/grafana:11.3.0
    restart: unless-stopped
    depends_on:
      - influxdb
    ports:
      - "3000:3000"
    environment:
      GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_PASSWORD}
      INFLUX_TOKEN: ${INFLUX_TOKEN}
      INFLUX_ORG: ${INFLUX_ORG}
      INFLUX_BUCKET: ${INFLUX_BUCKET}
    volumes:
      - grafana-data:/var/lib/grafana
      - ./grafana/provisioning:/etc/grafana/provisioning:ro
      - ./grafana/dashboards:/var/lib/grafana/dashboards:ro

volumes:
  influxdb-data:
  influxdb-config:
  grafana-data:

The health check matters. Without it, Telegraf can start before InfluxDB finishes its first-run setup, fail its first writes and fill the logs with errors. condition: service_healthy makes Compose wait until influx ping succeeds.

Step 3: Telegraf Configuration

Telegraf substitutes ${VAR} references from its environment, so the config file contains no secrets.

# telegraf/telegraf.conf
[agent]
  interval = "10s"
  flush_interval = "10s"
  omit_hostname = true

[[inputs.mqtt_consumer]]
  servers = ["${MQTT_SERVER}"]
  topics = ["${MQTT_TOPIC_PREFIX}/+/+/telemetry"]
  qos = 0
  client_id = "${MQTT_CLIENT_ID}"
  username = "${MQTT_USERNAME}"
  password = "${MQTT_PASSWORD}"

  name_override = "sensor"
  topic_tag = ""
  data_format = "json"
  tag_keys = ["device"]
  json_time_key = "ts"
  json_time_format = "unix_ms"

  [[inputs.mqtt_consumer.topic_parsing]]
    topic = "${MQTT_TOPIC_PREFIX}/+/+/telemetry"
    tags = "_/_/_/line/_/_"

[[outputs.influxdb_v2]]
  urls = ["http://influxdb:8086"]
  token = "${INFLUX_TOKEN}"
  organization = "${INFLUX_ORG}"
  bucket = "${INFLUX_BUCKET}"

Note the URL http://influxdb:8086: inside the Compose network, containers reach each other by service name, not localhost. The tags pattern assumes a three-level prefix like connectmq/demo/a1b2c3; if your prefix has a different depth, adjust the number of leading underscores. For a line-by-line explanation of these options, see the Telegraf MQTT consumer guide.

Step 4: Provision the Grafana Data Source

Provisioning creates the InfluxDB data source on startup, so nobody has to click through the UI. Grafana also expands environment variables in provisioning files.

# grafana/provisioning/datasources/influxdb.yml
apiVersion: 1
datasources:
  - name: InfluxDB
    uid: influxdb
    type: influxdb
    access: proxy
    url: http://influxdb:8086
    isDefault: true
    jsonData:
      version: Flux
      organization: ${INFLUX_ORG}
      defaultBucket: ${INFLUX_BUCKET}
    secureJsonData:
      token: ${INFLUX_TOKEN}

The fixed uid lets the dashboard reference this data source reliably.

Step 5: Provision a Ready-Made Dashboard

First tell Grafana where dashboards live:

# grafana/provisioning/dashboards/dashboards.yml
apiVersion: 1
providers:
  - name: mqtt
    folder: MQTT
    type: file
    options:
      path: /var/lib/grafana/dashboards

Then add the dashboard itself. It has a temperature time series per device and a stat panel with the number of reporting devices.

{
  "uid": "mqtt-sensors",
  "title": "MQTT Sensors",
  "schemaVersion": 39,
  "refresh": "10s",
  "time": { "from": "now-1h", "to": "now" },
  "panels": [
    {
      "id": 1,
      "type": "timeseries",
      "title": "Temperature by device",
      "gridPos": { "x": 0, "y": 0, "w": 18, "h": 10 },
      "datasource": { "type": "influxdb", "uid": "influxdb" },
      "fieldConfig": { "defaults": { "unit": "celsius" }, "overrides": [] },
      "targets": [
        {
          "refId": "A",
          "query": "from(bucket: \"sensors\")\n  |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n  |> filter(fn: (r) => r._measurement == \"sensor\" and r._field == \"temperature\")\n  |> group(columns: [\"device\"])\n  |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)"
        }
      ]
    },
    {
      "id": 2,
      "type": "stat",
      "title": "Reporting devices (last 5 min)",
      "gridPos": { "x": 18, "y": 0, "w": 6, "h": 10 },
      "datasource": { "type": "influxdb", "uid": "influxdb" },
      "targets": [
        {
          "refId": "A",
          "query": "from(bucket: \"sensors\")\n  |> range(start: -5m)\n  |> filter(fn: (r) => r._measurement == \"sensor\" and r._field == \"temperature\")\n  |> group()\n  |> distinct(column: \"device\")\n  |> count()"
        }
      ]
    }
  ]
}

If you changed the bucket name in .env, change "sensors" in the two queries as well. You can also import this JSON through Dashboards, then New, then Import in any existing Grafana instance.

Step 6: Start the Stack and Send Data

cd tig-mqtt
docker compose up -d
docker compose ps
docker compose logs -f telegraf

Now publish data. The quickest continuous source is our Python MQTT device simulator; run it with --prefix connectmq/demo/a1b2c3 so it matches MQTT_TOPIC_PREFIX. A single test message also works:

mosquitto_pub -h broker.connectmq.com -p 1883 -i pub-a1b2c3 \
  -t connectmq/demo/a1b2c3/line1/sensor-0001/telemetry \
  -m "{\"device\":\"sensor-0001\",\"ts\":$(date +%s%3N),\"temperature\":22.4,\"humidity\":41.0}"

Open http://localhost:3000, log in as admin with GRAFANA_PASSWORD, and open the MQTT Sensors dashboard in the MQTT folder.

Verify Each Layer in Order

When the dashboard stays empty, resist the urge to change several things at once. Walk down the pipeline and confirm each layer before moving to the next:

  1. Broker. Run mosquitto_sub -h broker.connectmq.com -p 1883 -i check-a1b2c3 -t "connectmq/demo/a1b2c3/#" -v. If nothing appears, the problem is the publisher, not the stack.
  2. Telegraf connection. docker compose logs telegraf should show the plugin connecting without errors. Authentication failures and topic parsing errors appear here.
  3. Telegraf parsing. Temporarily add an [[outputs.file]] block with files = ["stdout"] and restart Telegraf. Parsed metrics then appear in the logs as line protocol, which proves the JSON and topic mapping are right.
  4. InfluxDB. Run docker compose exec influxdb influx query 'from(bucket: "sensors") |> range(start: -5m) |> limit(n: 3)'. The CLI inside the container is already configured by the init setup.
  5. Grafana. Open the data source page and click Save and test, then check that the dashboard's time range covers the period when data was written.

In practice, most problems are found at step 1 or step 3: a topic prefix that differs by one character, or a payload that is not the JSON the config expects.

First-Run Pitfalls

Problem Cause Fix
Token from .env rejected InfluxDB init variables only apply to an empty volume docker compose down -v to reset, or create a new token in the UI
Telegraf: connection refused to localhost:8086 Wrong hostname inside containers Use http://influxdb:8086
Dashboard shows "No data" Topic prefix in .env differs from what is published Compare with mosquitto_sub -t "connectmq/demo/a1b2c3/#" -v
Telegraf: topic parsing error Prefix depth changed but tags pattern did not Match the number of _ segments to the topic levels
Grafana data source error Token or org mismatch Check the data source page; values come from .env
Stack works, but data disappears after down -v Volumes deleted Use docker compose down without -v

Moving to a Private Broker

For real equipment, only .env changes. Point the stack at a fully managed MQTT service such as ConnectMQ:

MQTT_SERVER=ssl://broker.connectmq.com:8883
MQTT_TOPIC_PREFIX=connectmq/factory1/production
MQTT_CLIENT_ID=YOUR_CLIENT_ID
MQTT_USERNAME=YOUR_USERNAME
MQTT_PASSWORD=YOUR_PASSWORD

The certificate is issued by Let's Encrypt, so Telegraf trusts it without extra TLS settings. Run docker compose up -d again to recreate the Telegraf container with the new values. Also place Grafana behind a reverse proxy with HTTPS before exposing it beyond your machine, and back up the influxdb-data volume.

When the dashboard is in place, add Grafana alerts for IoT sensor data on the same queries. For broader client choices on the device side, see the MQTT client libraries guide, and the Docker Compose documentation covers health checks and environment files in detail.

Get Started with ConnectMQ

Use the public MQTT test broker to bring up the stack today with simulated data. For production, ConnectMQ runs a fully managed MQTT broker with TLS, MQTT 5 and a portal where you can watch topics live before Telegraf ever sees them. Create a free Serverless broker at app.connectmq.com with no credit card, then paste the credentials into .env. If you are weighing hosting options, read our guide to the cloud MQTT broker.

Frequently Asked Questions

Can I run the MQTT broker inside the same Docker Compose file?

You can, but a shared, managed broker is usually simpler to operate and lets other consumers use the same data. Keeping it outside Compose also means restarting the stack never interrupts your devices.

Does this TIG stack Docker Compose setup work with InfluxDB 3?

Not unchanged. InfluxDB 3 uses a different image, setup procedure and query languages, so the init variables and Flux queries here apply to InfluxDB 2.7.

How do I add more dashboards?

Drop additional dashboard JSON files into grafana/dashboards. Grafana picks them up automatically through the file provider.

How much disk space will the data use?

It depends on the number of series and the message rate. The bucket's 30-day retention keeps growth bounded; lower it or downsample older data if disk is tight.

Related articles

Best MQTT CLI Tools for Testing, Scripting and Debugging

A hands-on comparison of mosquitto_pub/sub, MQTTX CLI, HiveMQ MQTT CLI, the MQTT.js CLI, curl and mqttui, with the same test run against broker.connectmq.com so you can choose the right tool for scripting and debugging.

8 min read

All articles →

Your private MQTT broker is a few clicks away

Create an account, get your broker credentials and connect your first device — no servers to install, patch or scale.