Deploy a complete Azure IoT infrastructure with one
terraform apply. IoT Hub + Event Hub + Log Analytics + monitoring — all wired together.
| Resource | Type | Notes |
|---|---|---|
| Resource Group | azurerm_resource_group |
Container for all resources |
| IoT Hub | azurerm_iothub |
F1 free tier — 8,000 msgs/day, 500 devices |
| Event Hub Namespace | azurerm_eventhub_namespace |
Standard, 1 TU |
| Event Hub | azurerm_eventhub |
2 partitions, 1-day retention |
| Consumer Group | azurerm_eventhub_consumer_group |
Dedicated for the monitor dashboard |
| Auth Rules | azurerm_eventhub_authorization_rule |
iothub-send (send) + monitor-listen (listen) |
| Log Analytics | azurerm_log_analytics_workspace |
30-day retention |
| Diagnostic Settings | IoT Hub + Event Hub → Log Analytics | Connections, telemetry, routing logs |
| Metric Alert | azurerm_monitor_metric_alert |
Alert when dropped telemetry > 10 |
| IoT Device | via az iot hub device-identity |
N registered devices (default: 1) |
graph LR
subgraph EDGE["🔌 Edge"]
ESP32["ESP32-C3\n+ BMP280 / BME280"]
end
subgraph AZURE["☁️ Microsoft Azure"]
IOTHUB["🔷 IoT Hub\nF1 Free"]
EH["🔷 Event Hub\nStandard"]
LOGS["🔷 Log Analytics\n30 days"]
ALERT["🔔 Alert\nDropped Msgs"]
IOTHUB -->|"Route"| EH
IOTHUB -.->|"Diagnostics"| LOGS
EH -.->|"Diagnostics"| LOGS
IOTHUB -.-> ALERT
end
subgraph DASH["🖥️ Dashboard"]
FLASK["Flask + Chart.js"]
end
ESP32 ==>|"D2C"| IOTHUB
EH -->|"Consumer Group"| FLASK
style AZURE fill:#e8f1fb,stroke:#0078d4,stroke-width:3px
style EDGE fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
style DASH fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
Full architecture diagram: see README.md
- Terraform ≥ 1.5 — Download
- Azure CLI — Download
- IoT extension for Azure CLI:
az extension add --name azure-iot
- An Azure account with an active subscription
Every command shows expected output so you always know if it worked.
az loginWhat happens: the browser opens — sign in with your Azure account. The terminal then shows:
[
{
"name": "Your Subscription Name",
"id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
...
}
]❌ Browser doesn't open? Try
az login --use-device-code.
If you're not already in the terraform folder:
cd terraform.\scripts\setup-tfstate-backend.ps1What you'll see (~1 minute):
Creating resource group 'globalazure-tfstate-rg'...
Creating storage account '...'...
Creating container 'tfstate'...
✅ Backend configuration written to backend.tfvars
Copy-Item terraform.auto.tfvars.example terraform.auto.tfvarsNo output? That's correct.
Find your Subscription ID:
az account show --query id -o tsv✅ You'll see a string like
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. Copy it.
Open the file:
notepad terraform.auto.tfvarsFind the line subscription_id = "ENTER-YOUR-SUBSCRIPTION-ID-HERE" and replace the placeholder with your Subscription ID:
subscription_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"Save (Ctrl+S) and close Notepad.
terraform init -backend-config=backend.tfvarsWhat you'll see:
Initializing the backend...
...
Terraform has been successfully initialized!
❌ "Backend configuration changed"? Type
yesto migrate.
terraform planWhat you'll see: a list of ~12 Azure resources that will be created. Review them if you want (no changes are made yet).
terraform applyTerraform shows the plan again and asks:
Do you want to perform these actions?
Enter a value:
Type yes and press Enter. Wait 2–3 minutes.
What you'll see at the end:
Apply complete! Resources: 12 added, 0 changed, 0 destroyed.
❌ Error about IoT Hub region? Open
terraform.auto.tfvarsand changeiothub_locationto"northeurope".
# All connection strings in .env format
terraform output -raw dot_env_content
# Individual values
terraform output iothub_name
terraform output eventhub_name
terraform output -raw iothub_service_connection_string
terraform output -raw eventhub_listen_connection_stringaz iot hub device-identity connection-string show `
--hub-name (terraform output -raw iothub_name) `
--device-id globalazure-kit-device-0 `
-o tsvterraform output -raw dot_env_content | Out-File -FilePath ../monitor/.env -Encoding UTF8Infrastructure is ready! Now:
- Wire the hardware — follow the ESP32-C3 quick-start guide (§1–§7)
- Flash the firmware — see firmware/README.md
- Start the dashboard — see monitor/README.md
Diagnostic logs from IoT Hub and Event Hub are sent to Log Analytics.
Open Azure Portal → Log Analytics workspace → Logs and try these KQL queries:
AzureDiagnostics
| where ResourceProvider == "MICROSOFT.DEVICES"
| where Category == "Connections"
| where TimeGenerated > ago(24h)
| project TimeGenerated, OperationName, ResultType, CallerLocalAddress
| order by TimeGenerated descAzureDiagnostics
| where ResourceProvider == "MICROSOFT.DEVICES"
| where Category == "DeviceTelemetry"
| where TimeGenerated > ago(1h)
| summarize count() by bin(TimeGenerated, 5m)
| render timechart| Parameter | File | Default | Description |
|---|---|---|---|
subscription_id |
terraform.auto.tfvars |
— | Your Azure Subscription ID |
project_prefix |
terraform.auto.tfvars |
globalazure-kit |
Resource name prefix |
location |
terraform.auto.tfvars |
italynorth |
Region for Event Hub, Log Analytics |
iothub_location |
terraform.auto.tfvars |
westeurope |
Region for IoT Hub (not available everywhere) |
device_count |
terraform.auto.tfvars |
1 |
Number of IoT devices to register |
To destroy all resources:
terraform destroyNote: the tfstate Resource Group (
globalazure-tfstate-rg) is NOT removed byterraform destroy. To remove it manually:az group delete --name globalazure-tfstate-rg --yes
terraform/
├── main.tf # Provider, backend, locals
├── variables.tf # Input variables
├── resources.tf # IoT Hub, Event Hub, devices
├── monitoring.tf # Log Analytics, diagnostics, alerts
├── outputs.tf # Connection strings, .env output
├── backend.tfvars.example # Backend config template
├── terraform.auto.tfvars.example # Variables template
├── .gitignore # Excludes tfstate and sensitive files
├── scripts/
│ └── setup-tfstate-backend.ps1 # Bootstrap storage for tfstate
└── README.md # This guide
| Resource | Cost |
|---|---|
| IoT Hub F1 | Free (8,000 msgs/day) |
| Event Hub Standard | ~€10/month (1 TU) |
| Log Analytics | ~€2.50/GB ingested |
| Storage (tfstate) | ~€0.02/month |
| Estimated total | ~€10–15/month |
For a workshop lasting a few hours, the cost is negligible. Remember to run
terraform destroywhen done!
You forgot -backend-config=backend.tfvars:
terraform init -backend-config=backend.tfvarsChange iothub_location in terraform.auto.tfvars. Use westeurope or northeurope.
Install the IoT extension:
az extension add --name azure-iotNormal — the provisioner shows a warning but continues.
az account list --output table
az account set --subscription "NAME-OR-ID"
Engineered by ExtraNet, AI-accelerated with GitHub Copilot
Global Azure Torino 2026