Security & Deployment

Production IoT Deployment

Scale your IoT system from prototype to production — device provisioning, fleet management, monitoring, cost optimization, and operating thousands of devices reliably.

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 management

Zero-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.conf

Cost 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

CategoryCheckpointRequired
SecurityAll devices have unique credentials✓ MUST
SecurityTLS on all connections✓ MUST
SecurityNetwork segmented✓ MUST
ProvisioningZero-touch provisioning working✓ MUST
FirmwareOTA mechanism tested✓ MUST
MonitoringDevice heartbeat tracking✓ MUST
MonitoringAlerting on device offline✓ MUST
DataRetention policy configured✓ MUST
OpsRunbook for common failures✓ MUST
OpsOn-call rotation defined✓ MUST
CostMonthly cost estimate done✓ MUST
ScaleBroker load testedRecommended
SecurityCertificate rotation automatedRecommended
ResilienceBroker failover testedRecommended

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.