Production IoT Deployment
Getting 5 devices working on your desk is very different from operating 5,000 devices in the field. Production IoT requires automated provisioning, health monitoring, cost controls, and processes for handling real-world failures.
The Production Readiness Gap
Prototype (10 devices) Production (10,000 devices)
──────────────────────── ────────────────────────────
Manual WiFi setup Zero-touch provisioning
Hardcoded credentials Unique per-device secrets
Single MQTT broker Clustered, load-balanced broker
No monitoring Full observability stack
Manual firmware updates Automated staged OTA
Delete and reflash on failure Remote diagnosis + recovery
One developer knows everything Runbooks, on-call rotation
$0 infrastructure Cloud cost managementZero-Touch Provisioning
Manufacturing provision — devices receive their identity and credentials at the factory or first boot.
python
# provisioning_server.py — assigns credentials to new devices
from fastapi import FastAPI, HTTPException
import secrets
import sqlite3
import json
from datetime import datetime
app = FastAPI()
def get_db():
return sqlite3.connect("fleet.db")
@app.post("/provision")
async def provision_device(body: dict):
"""
Called by a new device on first boot.
Device sends its hardware ID (MAC address, chip ID).
Server returns MQTT credentials and configuration.
"""
hardware_id = body.get("hardware_id")
device_type = body.get("device_type", "sensor-node")
if not hardware_id:
raise HTTPException(400, "hardware_id required")
db = get_db()
# Check if already provisioned
existing = db.execute(
"SELECT * FROM devices WHERE hardware_id = ?", (hardware_id,)
).fetchone()
if existing:
raise HTTPException(409, "Device already provisioned")
# Generate unique credentials
device_id = f"{device_type}-{hardware_id[-6:].lower()}"
mqtt_username = device_id
mqtt_password = secrets.token_urlsafe(32)
api_key = secrets.token_hex(16)
# Store in fleet database
db.execute("""
INSERT INTO devices
(device_id, hardware_id, device_type, mqtt_username, mqtt_password,
api_key, provisioned_at, firmware_version)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
""", (
device_id, hardware_id, device_type,
mqtt_username, mqtt_password, api_key,
datetime.utcnow().isoformat(), "unknown"
))
db.commit()
db.close()
return {
"device_id": device_id,
"mqtt_username": mqtt_username,
"mqtt_password": mqtt_password,
"mqtt_broker": "mqtt.bigxstar.com",
"mqtt_port": 8883,
"telemetry_topic": f"devices/{device_id}/telemetry",
"command_topic": f"devices/{device_id}/commands",
"api_key": api_key,
"config": {
"reporting_interval_s": 60,
"alert_temp_high": 40.0,
"alert_temp_low": 5.0,
}
}python
# On-device provisioning flow (MicroPython)
import urequests, ujson, uos
PROVISION_URL = "https://api.bigxstar.com/provision"
CONFIG_FILE = "/config.json"
def provision():
"""Run on first boot if config.json doesn't exist."""
if CONFIG_FILE in uos.listdir('/'):
print("Already provisioned")
return load_config()
import machine
hardware_id = ':'.join(['%02X' % b for b in machine.unique_id()])
print(f"Provisioning device: {hardware_id}")
r = urequests.post(
PROVISION_URL,
json={"hardware_id": hardware_id, "device_type": "env-sensor"},
timeout=30
)
if r.status_code != 200:
raise RuntimeError(f"Provisioning failed: {r.status_code}")
config = r.json()
r.close()
with open(CONFIG_FILE, 'w') as f:
f.write(ujson.dumps(config))
print(f"Provisioned as: {config['device_id']}")
return config
def load_config():
with open(CONFIG_FILE, 'r') as f:
return ujson.loads(f.read())
config = provision()Fleet Management Dashboard
python
# fleet_manager.py — monitor all devices in the fleet
import paho.mqtt.client as mqtt
import json
import sqlite3
from datetime import datetime, timedelta
import time
class FleetManager:
def __init__(self):
self._db = sqlite3.connect("fleet.db", check_same_thread=False)
self._setup_db()
self._devices = {} # device_id → last_seen, status
def _setup_db(self):
self._db.execute("""
CREATE TABLE IF NOT EXISTS device_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp TEXT,
device_id TEXT,
event_type TEXT,
details TEXT
)
""")
self._db.commit()
def on_telemetry(self, device_id, data):
now = datetime.utcnow()
firmware = data.get("firmware_version", "unknown")
battery = data.get("battery_pct")
self._devices[device_id] = {
"last_seen": now.isoformat(),
"firmware": firmware,
"battery": battery,
"status": "online"
}
# Flag outdated firmware
LATEST_FIRMWARE = "1.5.0"
if firmware != LATEST_FIRMWARE and firmware != "unknown":
self._log_event(device_id, "OUTDATED_FIRMWARE",
f"Running v{firmware}, latest is v{LATEST_FIRMWARE}")
# Flag low battery
if battery and battery < 15:
self._log_event(device_id, "LOW_BATTERY",
f"Battery at {battery}%")
def get_offline_devices(self, offline_threshold_minutes=10):
"""Devices that haven't reported in N minutes."""
cutoff = datetime.utcnow() - timedelta(minutes=offline_threshold_minutes)
offline = []
for dev_id, info in self._devices.items():
last = datetime.fromisoformat(info["last_seen"])
if last < cutoff:
offline.append({
"device_id": dev_id,
"last_seen": info["last_seen"],
"minutes_ago": int((datetime.utcnow() - last).total_seconds() / 60)
})
return sorted(offline, key=lambda x: x["minutes_ago"], reverse=True)
def get_fleet_summary(self):
online = sum(1 for d in self._devices.values() if d["status"] == "online")
offline = self.get_offline_devices()
firmwares = {}
for d in self._devices.values():
fw = d.get("firmware", "unknown")
firmwares[fw] = firmwares.get(fw, 0) + 1
batteries = [d["battery"] for d in self._devices.values()
if d.get("battery") is not None]
return {
"total_devices": len(self._devices),
"online": online,
"offline": len(offline),
"firmware_versions": firmwares,
"avg_battery": round(sum(batteries)/len(batteries), 1) if batteries else None,
"low_battery_count": sum(1 for b in batteries if b < 20),
}
def _log_event(self, device_id, event_type, details):
self._db.execute("""
INSERT INTO device_events (timestamp, device_id, event_type, details)
VALUES (?, ?, ?, ?)
""", (datetime.utcnow().isoformat(), device_id, event_type, details))
self._db.commit()
print(f"[{event_type}] {device_id}: {details}")
fm = FleetManager()
# Simulated fleet activity
import random
for i in range(50):
device_id = f"env-sensor-{i:04d}"
fm.on_telemetry(device_id, {
"firmware_version": random.choice(["1.4.2", "1.5.0", "1.5.0"]),
"battery_pct": random.randint(10, 95),
"temperature": round(20 + random.uniform(0, 10), 1)
})
summary = fm.get_fleet_summary()
print(json.dumps(summary, indent=2))
offline = fm.get_offline_devices(offline_threshold_minutes=5)
print(f"Offline devices: {len(offline)}")MQTT Broker Scaling
yaml
# docker-compose.yml — HiveMQ cluster (3 brokers)
version: '3.8'
services:
hivemq-1:
image: hivemq/hivemq4
ports:
- "1883:1883"
- "8883:8883"
- "8080:8080"
environment:
- HIVEMQ_CLUSTER_DISCOVERY=STATIC
- HIVEMQ_CLUSTER_HOST=hivemq-1
- HIVEMQ_CLUSTER_MEMBERS=hivemq-1:7800,hivemq-2:7800,hivemq-3:7800
volumes:
- ./hivemq-config:/opt/hivemq/conf
- ./certs:/opt/hivemq/certs
hivemq-2:
image: hivemq/hivemq4
environment:
- HIVEMQ_CLUSTER_DISCOVERY=STATIC
- HIVEMQ_CLUSTER_MEMBERS=hivemq-1:7800,hivemq-2:7800,hivemq-3:7800
hivemq-3:
image: hivemq/hivemq4
environment:
- HIVEMQ_CLUSTER_DISCOVERY=STATIC
- HIVEMQ_CLUSTER_MEMBERS=hivemq-1:7800,hivemq-2:7800,hivemq-3:7800
nginx-lb:
image: nginx
ports:
- "8883:8883"
volumes:
- ./nginx.conf:/etc/nginx/nginx.confCost Optimization
python
# cost_calculator.py — estimate monthly IoT infrastructure costs
def estimate_monthly_cost(
num_devices,
readings_per_hour,
avg_payload_bytes,
cloud_provider="aws_iot"
):
"""
Calculate estimated monthly IoT cloud costs.
"""
readings_per_month = num_devices * readings_per_hour * 24 * 30
data_gb_per_month = readings_per_month * avg_payload_bytes / 1e9
pricing = {
"aws_iot": {
"per_million_msgs": 1.00, # $1/million messages
"per_gb_egress": 0.09, # $0.09/GB egress
"rule_engine_pm": 0.15, # $0.15/million rule executions
}
}
p = pricing[cloud_provider]
msg_cost = readings_per_month / 1e6 * p["per_million_msgs"]
egress_cost = data_gb_per_month * p["per_gb_egress"]
rules_cost = readings_per_month / 1e6 * p["rule_engine_pm"]
monthly_cost = msg_cost + egress_cost + rules_cost
print(f"Fleet: {num_devices} devices × {readings_per_hour} readings/hr")
print(f"Volume: {readings_per_month:,.0f} msgs/month ({data_gb_per_month:.2f} GB)")
print(f"Costs:")
print(f" Messages: ${msg_cost:.2f}/month")
print(f" Data egress: ${egress_cost:.2f}/month")
print(f" Rule engine: ${rules_cost:.2f}/month")
print(f" TOTAL: ${monthly_cost:.2f}/month")
# Cost optimization tips
if readings_per_hour > 12:
print("\nTip: Edge aggregation could reduce readings to 1/hr and save "
f"${monthly_cost * (1 - 1/readings_per_hour):.2f}/month")
# Small deployment
estimate_monthly_cost(100, 12, 256)
print()
# Large deployment
estimate_monthly_cost(10000, 12, 256)Production Monitoring Stack
yaml
# Minimum production monitoring (docker-compose)
services:
prometheus:
image: prom/prometheus
ports: ["9090:9090"]
volumes: ["./prometheus.yml:/etc/prometheus/prometheus.yml"]
grafana:
image: grafana/grafana-oss
ports: ["3000:3000"]
environment:
- GF_SECURITY_ADMIN_PASSWORD=your-strong-password
influxdb:
image: influxdb:2.7
ports: ["8086:8086"]
volumes: ["influxdb-data:/var/lib/influxdb2"]
alertmanager:
image: prom/alertmanager
ports: ["9093:9093"]
volumes: ["./alertmanager.yml:/etc/alertmanager/alertmanager.yml"]Production Deployment Checklist
| Category | Checkpoint | Required |
|---|---|---|
| Security | All devices have unique credentials | ✓ MUST |
| Security | TLS on all connections | ✓ MUST |
| Security | Network segmented | ✓ MUST |
| Provisioning | Zero-touch provisioning working | ✓ MUST |
| Firmware | OTA mechanism tested | ✓ MUST |
| Monitoring | Device heartbeat tracking | ✓ MUST |
| Monitoring | Alerting on device offline | ✓ MUST |
| Data | Retention policy configured | ✓ MUST |
| Ops | Runbook for common failures | ✓ MUST |
| Ops | On-call rotation defined | ✓ MUST |
| Cost | Monthly cost estimate done | ✓ MUST |
| Scale | Broker load tested | Recommended |
| Security | Certificate rotation automated | Recommended |
| Resilience | Broker failover tested | Recommended |
Summary
- Zero-touch provisioning assigns unique credentials to each device automatically — no manual configuration
- Fleet management requires tracking firmware versions, battery levels, last-seen times, and event logs
- MQTT broker clustering (HiveMQ, EMQX) handles tens of thousands of concurrent connections
- Cost optimization: edge aggregation can reduce cloud messaging costs by 90%+ on high-frequency sensors
- The production monitoring stack (Prometheus + Grafana + InfluxDB + Alertmanager) is non-negotiable
- Run through the production checklist before deploying any device that cannot be physically accessed
In the final topic, we'll look at the future of IoT — Edge AI, 5G, Matter, Digital Twins, and what comes next.