Automated deployment of Azure IoT Operations (AIO) on edge devices with industrial IoT applications.
- What You Get
- Why not use codespaces from the docs?
- Quick Start
- Prerequisites
- Installation
- Key Documentation
- What's Included
- Configuration
- Next Steps
- Support
- ⚡ One-command edge setup - Automated K3s cluster with Azure IoT Operations
- 🏭 Industrial IoT apps - Factory simulator, MQTT historian, data processors
- ☁️ Cloud integration - Dataflow pipelines to Azure (ADX, Event Hubs, Fabric) using Managed Identity — no secrets to manage.
- 🔧 Production-ready - Separation of edge and cloud configuration for security
- 💻 Single-machine (AKS-EE) - Run everything on one Windows laptop with session-bootstrap.ps1
For detailed technical information, see README_ADVANCED.md
The docs have a very clean "one click" deployment in the MSFT docs. It's a great first step, especially if you just want to see the tools.
- That will live in its own environment and you won't be able to connect it to your signals or your devices.
- This version will help you set up AIO in the actual environment where you do your IoT operations.
- This is much closer to a production-level deployment.
- This instance will last as long as you want to keep it.
As the end-goal is an IoT solution, this repo has a preference for installing on hardware over virtualization. The goal is that you can put this in your IoT environment, validate the build, and then migrate to a production version.
The goal here is to install AIO on an Ubuntu machine (like a local NUC, PC, or a VM) so that you can get working quickly on your dataflow pipelines and get data into Fabric quickly.
- if you are in a purely testing or validation phase you can create a quick VM using this process
- if you are building on a Windows machine using AKS Edge Essentials, see the Single Windows Machine (AKS-EE) section below.
Using AKS Edge Essentials (Windows-based edge)?
Follow the Deploy AIO on AKS Edge Essentials guide to set up your edge cluster, then skip to step 4 (Azure Configuration from Windows Machine) below. Steps 1–3b do not apply to AKS-EE.
Once you have setup AIO via this process, you should be able to do everything that you want in the cloud without touching the Ubuntu machine again.
- arc_build_linux\installer.sh
- arc_build_linux\arc_enable.ps1
- external_configuration\grant_entra_id_roles.ps1
- external_configuration\External-Configurator.ps1
Specific commands for them are below.
Note Installing AIO can be different depending on your setup. In many cases, you have to run some scripts multiple times or in different order. The log messages in each script should tell you what to do next.
- Hardware: Ubuntu machine with 16GB RAM, 4 CPU cores, 50GB disk
- Azure: Active subscription with admin access
- Network: Internet connectivity (edge device and management machine)
- PowerShell 7+ (strongly recommended — 5.1 will produce a warning but may still work)
Download: https://aka.ms/install-powershell - Azure CLI ≥ 2.64.0
Install: https://aka.ms/installazurecliwindows
Check version:az --version
Upgrade:az upgrade - Required CLI extensions (install once, then update with
az extension update --name <ext>):az extension add --upgrade --name azure-iot-ops az extension add --upgrade --name connectedk8s
- Execution Policy — the scripts in this repo are unsigned. Run this once at the start of each PS session:
Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process -Force
Option A — Download ZIP (no Git required):
Click the green Code button on this GitHub page and choose Download ZIP, then extract to a local working directory.
Option B — Clone with Git:
# Install git if not already installed
sudo apt update && sudo apt install -y git
git clone https://github.com/BillmanH/learn-iot.git
cd learn-iotBefore running any installation scripts, create and configure aio_config.json:
cd config
cp quikstart_config.template aio_config.jsonEdit aio_config.json with your settings:
- Cluster name for your edge device
- Optional tools to install (k9s, mqtt-viewer, ssh, and powershell)
This config file controls the edge deployment. Review it carefully before proceeding.
cd arc_build_linux
bash installer.shWhat it does: Installs K3s, kubectl, Helm, and prepares the cluster for Azure IoT Operations
Time: ~10-15 minutes
Output: config/cluster_info.json (needed for next step)
Note: System may restart during installation. This is normal. Rerun the script after restart to continue.
After installer.sh completes, connect the cluster to Azure Arc:
# Still on the edge device (PowerShell is installed by installer.sh)
pwsh ./arc_enable.ps1What it does:
- Logs into Azure (interactive device code flow)
- Creates resource group if needed
- Connects the K3s cluster to Azure Arc
- Enables required Arc features (custom-locations, OIDC, workload identity)
- Configures K3s to use the Arc OIDC issuer (required for Key Vault secret sync)
Time: ~5 minutes
Why on the edge device?: Arc enablement requires kubectl access to the cluster, which isn't available remotely.
After this you should see the core arc-kubernetes components on your edge device.
Note: If you need remote access via Arc proxy, see README_ADVANCED.md for RBAC setup.
These scripts are idempotent — it is normal and expected to run them multiple times. Common reasons include adjusting a parameter, recovering from a partial failure, or re-running
grant_entra_id_roles.ps1after new resources have been created byExternal-Configurator.ps1. Each run picks up where it left off.
Choose one of three ways to provide your Azure settings to the scripts:
Option A — Paste values directly in your terminal (quickest, no file editing)
$env:AZURE_SUBSCRIPTION_ID = "your-subscription-id"
$env:AZURE_LOCATION = "eastus2" # e.g. eastus2, westus, westeurope
$env:AZURE_RESOURCE_GROUP = "rg-my-iot" # created if it does not exist
$env:AKSEDGE_CLUSTER_NAME = "my-cluster" # must be lowercase, no spaces
$env:AZURE_CONTAINER_REGISTRY = "" # short name only, e.g. myregistry (leave blank to auto-generate)
# Tenant ID is optional - only needed if you have multiple Azure tenants
# $env:AZURE_TENANT_ID = "your-tenant-id" # az account show --query tenantId -o tsv
az login # add --tenant $env:AZURE_TENANT_ID if you have multiple tenants
az account set --subscription $env:AZURE_SUBSCRIPTION_ID
cd external_configuration
Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process -Force
.\grant_entra_id_roles.ps1
.\External-Configurator.ps1Resource names (Key Vault, Storage Account, Schema Registry) are not settable via environment variables — they auto-generate from the cluster/resource group name. To specify custom names, use Option C (
aio_config.json) instead.
Option B — Edit session-bootstrap.ps1 and run it (recommended if you do this repeatedly)
Fill in the required variables in external_configuration\session-bootstrap.ps1 and save. Then run it once per PS7 session — it sets all variables and logs you in automatically. This is especially useful if you open new terminal windows frequently or return to this setup over multiple sessions.
$AZ_SUBSCRIPTION_ID = "your-subscription-id"
$AZ_TENANT_ID = "" # optional - only needed if you have multiple Azure tenants
# az account show --query tenantId -o tsv
$AZ_LOCATION = "eastus2"
$AZ_RESOURCE_GROUP = "rg-my-iot" # created if it does not exist
$AKS_EDGE_CLUSTER_NAME = "my-cluster" # must be lowercase, no spaces
$AZ_CONTAINER_REGISTRY = "" # short name only, e.g. myregistry (leave blank to auto-generate)
# Key Vault, Storage Account, and Schema Registry names are not
# settable here - they auto-generate. To customize them, use Option C.cd external_configuration
Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process -Force
.\session-bootstrap.ps1
.\grant_entra_id_roles.ps1
.\External-Configurator.ps1Option C — Copy aio_config.json from the edge device (Linux/K3s path default, and the only option for custom resource names)
Transfer the config/ folder from your edge device to your Windows management machine (or copy aio_config.json directly). This is also the only way to specify custom names for Key Vault, Storage Account, and Schema Registry — leave them blank to auto-generate:
{
"azure": {
"subscription_id": "your-subscription-id",
"resource_group": "rg-my-iot",
"location": "eastus2",
"cluster_name": "my-cluster",
"storage_account_name": "", // leave blank to auto-generate
"key_vault_name": "", // leave blank to auto-generate
"container_registry": "" // leave blank to auto-generate
}
}cd external_configuration
Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process -Force
.\grant_entra_id_roles.ps1
.\External-Configurator.ps1Note: Options A and B override any values in
aio_config.jsonand work for both the Linux/K3s path and the AKS-EE path.
Single-node or demo machine? Add
-DemoModetoExternal-Configurator.ps1to reduce broker RAM from ~15.8 GB to ~303 MiB — NOT for production use.
Grant permissions separately? It may be that the person who has permission to assign Azure roles is different from the person deploying. Run
grant_entra_id_roles.ps1first with the appropriate identity, thenExternal-Configurator.ps1separately. To grant permissions to a specific user, pass their Object ID (not email):# Get your Object ID: az ad signed-in-user show --query id -o tsv .\grant_entra_id_roles.ps1 -AddUser 12345678-1234-1234-1234-123456789abc
⚠️ IMPORTANT: You may need to rungrant_entra_id_roles.ps1multiple times!
The script grants permissions to resources that exist at the time it runs. IfExternal-Configurator.ps1creates new resources (like Schema Registry) and then fails on role assignments, simply rungrant_entra_id_roles.ps1again to grant permissions to the newly created resources, then re-runExternal-Configurator.ps1.
💡 MOST COMMON ISSUE: Moving to the next step before clusters are ready
If you get errors, don't just re-run the script immediately. The error messages include troubleshooting steps - read them carefully. Common issues include:
- Arc cluster showing "Not Connected" (check Arc agent pods on edge device)
- Role assignment failures (run
grant_entra_id_roles.ps1first)- IoT Operations deployment failing (ensure Arc is fully connected)
Always verify the previous step completed successfully before moving on. Use
kubectl get pods -n azure-arcon the edge device to confirm Arc agents are running.
WARNING the field kubeconfig_base64 in cluster_info.json contains a secret. Be careful with that.
What it does: Deploys AIO infrastructure (storage, Key Vault, schema registry) and IoT Operations
Time: ~15-20 minutes
Note: Arc enablement was already done on the edge device in step 3b
SSH into your Linux edge device and run:
# Check pods are running
kubectl get pods -n azure-iot-operationsOnce AIO is running, subscribe to all MQTT topics to confirm messages are flowing:
kubectl exec -it -n azure-iot-operations deploy/aio-broker-frontend -- \
mosquitto_sub -h localhost -p 18883 -t '#' -vPress Ctrl+C to stop. If you see messages arriving, AIO is working end-to-end.
If you are running both AKS Edge Essentials (edge) and the Azure management scripts on the same Windows laptop, session-bootstrap.ps1 is an optional convenience helper — or you can skip it entirely and paste values directly in your terminal.
- PowerShell 7+ (
winget install Microsoft.PowerShell) - Azure CLI ≥ 2.64.0 (
winget install Microsoft.AzureCLI) azure-iot-opsandconnectedk8sextensions (see Prerequisites)
AKS Edge Essentials vs. AKS: In this path, Kubernetes runs locally on your Windows machine via AKS Edge Essentials (AKS-EE) — a lightweight, Microsoft-supported K8s distribution embedded in Windows. This is different from the Ubuntu/K3s path (which uses K3s) and from cloud-hosted AKS. AKS-EE is still Arc-enabled and managed through Azure the same way, but the cluster itself lives on your local machine rather than on a separate edge device or in the cloud.
Step 1 — Set your Azure context (choose one option)
Option A — Paste values directly in your terminal (quickest, no file editing):
Tip: Option A is the fastest way to get going — just paste and run. Option B is worth the one-time setup if you return to this workflow regularly or work across multiple terminal sessions.
$env:AZURE_SUBSCRIPTION_ID = "your-subscription-id"
$env:AZURE_LOCATION = "eastus2" # e.g. eastus2, westus, westeurope
$env:AZURE_RESOURCE_GROUP = "rg-my-iot" # created if it does not exist
$env:AKSEDGE_CLUSTER_NAME = "my-cluster" # must be lowercase, no spaces
$env:AZURE_CONTAINER_REGISTRY = "" # short name only, e.g. myregistry (leave blank to auto-generate)
# Tenant ID is optional - only needed if you have multiple Azure tenants
# $env:AZURE_TENANT_ID = "your-tenant-id" # az account show --query tenantId -o tsv
az login # add --tenant $env:AZURE_TENANT_ID if you have multiple tenants
az account set --subscription $env:AZURE_SUBSCRIPTION_IDResource names (Key Vault, Storage Account, Schema Registry) are not settable via environment variables — they auto-generate from the cluster/resource group name. To specify custom names, use Option B (session-bootstrap) which also reads from
aio_config.json, or copyaio_config.jsondirectly.
Option B — Use session-bootstrap.ps1 (recommended if you do this repeatedly):
Fill in the required variables in external_configuration\session-bootstrap.ps1 and save. Run it once at the start of each PS7 session — it sets all variables, including the $global:* variables for the AKS-EE quickstart, and logs you in automatically. Especially useful if you open new terminal windows frequently.
$AZ_SUBSCRIPTION_ID = "your-subscription-id"
$AZ_TENANT_ID = "" # optional - only needed if you have multiple Azure tenants
# az account show --query tenantId -o tsv
$AZ_LOCATION = "eastus2"
$AZ_RESOURCE_GROUP = "rg-my-iot" # created if it does not exist
$AKS_EDGE_CLUSTER_NAME = "my-cluster" # must be lowercase, no spaces
$AZ_CONTAINER_REGISTRY = "" # short name only, e.g. myregistry (leave blank to auto-generate)
# Key Vault, Storage Account, and Schema Registry names are not
# settable here - they auto-generate. To customize them, use aio_config.json.cd external_configuration
Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process -Force
.\session-bootstrap.ps1Step 2 — Set up your AKS-EE edge cluster
Follow the Deploy AIO on AKS Edge Essentials guide. If you used Option B above, the $global:* variables set by session-bootstrap.ps1 are picked up automatically by the AKS-EE quickstart.
Step 3 — Grant permissions and deploy AIO
After either option above, run:
.\grant_entra_id_roles.ps1
.\External-Configurator.ps1 -DemoMode # -DemoMode recommended for single-machine setupsTo grant permissions to a specific user instead of yourself, pass their Object ID:
.\grant_entra_id_roles.ps1 -AddUser 12345678-1234-1234-1234-123456789abc
Step 4 (Optional) — Deploy an edge module
These modules are not part of AIO itself. They are demo applications that generate simulated data or mimic industrial processes so you can see AIO working end-to-end without needing real hardware or live signals. They would not belong in a production system — replace them with your own data sources when you're ready.
Once AIO is running, you can push containerized applications to the edge cluster from your Windows machine. This step requires Docker Desktop running locally to build the image before pushing to ACR.
Deploy the factory MQTT simulator (edgemqttsim) as a first module:
# Deploy edgemqttsim — builds the image, pushes to ACR, and applies the K8s manifest
.\Deploy-EdgeModules.ps1 -ModuleName edgemqttsimIf the image is already built and in the registry (e.g. on a re-deploy), skip the Docker build step:
.\Deploy-EdgeModules.ps1 -ModuleName edgemqttsim -SkipBuildTo force a fresh redeployment of a module that is already running:
.\Deploy-EdgeModules.ps1 -ModuleName edgemqttsim -ForceTo deploy all modules configured in aio_config.json at once, omit -ModuleName:
.\Deploy-EdgeModules.ps1What it does: Builds the container image on your Windows machine, pushes it to the Azure Container Registry created by External-Configurator.ps1, then applies the Kubernetes deployment manifest to the edge cluster via Azure Arc proxy — no direct network access to the edge device required.
Available modules: edgemqttsim, hello-flask, sputnik, demohistorian
Note: The ACR registry endpoint must be registered in AIO before image pulls will succeed — this is done automatically by External-Configurator.ps1.
- Config Files Guide - Configuration file templates and outputs
- Linux Build Advanced - Advanced flags, troubleshooting, and cleanup scripts
arc_build_linux/installer.sh- Edge device installer (local infrastructure only)external_configuration/External-Configurator.ps1- Remote Azure configurator (cloud resources only)external_configuration/Deploy-EdgeModules.ps1- Automated deployment script for edge applications
- Edge MQTT Simulator - Comprehensive factory telemetry simulator
- Edge Historian - SQL-based historian with HTTP API for querying historical MQTT data
- Fabric Integration - Connecting AIO to Microsoft Fabric
- edgemqttsim - Factory equipment simulator (CNC, 3D printer, welding, etc.)
- demohistorian - SQL-based MQTT historian with HTTP API
- sputnik - Simple MQTT test publisher
- hello-flask - Basic web app for testing
arc_build_linux/- Edge device installation scripts (runs on Ubuntu)external_configuration/- Azure configuration scripts (runs on Windows)config/- Configuration files and cluster info outputsfabric_setup/- Microsoft Fabric Real-Time Intelligence integrationoperations/- Dataflow configurations for cloud connectivitymodules/- Deployable edge modules and ARM templates
Customize edge deployment via arc_build_linux/aio_config.json:
- Cluster name for your edge device
- Optional tools (k9s, MQTT viewers, SSH)
- Azure AD principal for Arc proxy access
Customize Azure deployment via config/aio_config.json:
- Azure subscription and resource group settings
- Location and namespace configuration
- Key Vault settings for secret management
container_registry— short name (e.g.myregistry) for the Azure Container Registry used byDeploy-EdgeModules.ps1; auto-generated if blank
After installation:
- View MQTT messages: See README_ADVANCED.md
- Deploy applications: See README_ADVANCED.md
- Connect to Fabric: See README_ADVANCED.md
- Troubleshooting: See README_ADVANCED.md
- README_ADVANCED.md - Detailed technical guide
- Application READMEs - Individual app documentation


