Tutorial: Develop IoT Edge modules using Windows containers

Applies to: yes icon IoT Edge 1.1


IoT Edge 1.1 end of support date was December 13, 2022. Check the Microsoft Product Lifecycle for information about how this product, service, technology, or API is supported. For more information about updating to the latest version of IoT Edge, see Update IoT Edge.

Use Visual Studio to develop and deploy code to Windows devices running IoT Edge.


IoT Edge 1.1 LTS is the last release channel that supports Windows containers. Starting with version 1.2, Windows containers are not supported. Consider using or moving to IoT Edge for Linux on Windows to run IoT Edge on Windows devices.

This tutorial walks through what it takes to develop and deploy your own code to an IoT Edge device. This tutorial is a useful prerequisite for the other tutorials, which go into more detail about specific programming languages or Azure services.

This tutorial uses the example of deploying a C# module to a Windows device. This example was chosen because it's the most common development scenario. If you're interested in developing in a different language, or plan on deploying Azure services as modules, this tutorial will still be helpful to learn about the development tools. Once you understand the development concepts, then you can choose your preferred language or Azure service to dive into the details.

In this tutorial, you learn how to:

  • Set up your development machine.
  • Use the IoT Edge tools for Visual Studio to create a new project.
  • Build your project as a container and store it in an Azure container registry.
  • Deploy your code to an IoT Edge device.


A development machine:

  • Windows 10 with 1809 update or newer.
  • You can use your own computer or a virtual machine, depending on your development preferences.
    • Make sure that your development machine supports nested virtualization. This capability is necessary for running a container engine, which you install in the next section.
  • Install Git.

An Azure IoT Edge device on Windows:

  • Install and manage Azure IoT Edge with Windows containers.
  • We recommend that you don't run IoT Edge on your development machine, but instead use a separate device if possible. This distinction between development machine and IoT Edge device more accurately mirrors a true deployment scenario, and helps to keep the different concepts straight.

Cloud resources:

  • A free or standard-tier IoT hub in Azure.

If you don't have an Azure subscription, create a free account before you begin.

Key concepts

This tutorial walks through the development of an IoT Edge module. An IoT Edge module, or sometimes just module for short, is a container that contains executable code. You can deploy one or more modules to an IoT Edge device. Modules perform specific tasks like ingesting data from sensors, performing data analytics or data cleaning operations, or sending messages to an IoT hub. For more information, see Understand Azure IoT Edge modules.

When developing IoT Edge modules, it's important to understand the difference between the development machine and the target IoT Edge device where the module will eventually be deployed. The container that you build to hold your module code must match the operating system (OS) of the target device. For Windows container development, this concept is simpler because Windows containers only run on Windows operating systems. But you could, for example, use your Windows development machine to build modules for Linux IoT Edge devices. In that scenario, you'd have to make sure that your development machine was running Linux containers. As you go through this tutorial, keep in mind the difference between development machine OS and the container OS.

This tutorial targets Windows devices running IoT Edge. Windows IoT Edge devices use Windows containers. We recommend using Visual Studio to develop for Windows devices, so that's what this tutorial will use. You can use Visual Studio Code as well, although there are differences in support between the two tools.

The following table lists the supported development scenarios for Windows containers in Visual Studio Code and Visual Studio.

Visual Studio Code Visual Studio 2017/2019
Azure services Azure Functions
Azure Stream Analytics
Languages C# (debugging not supported) C
More information Azure IoT Edge for Visual Studio Code
Azure IoT Hub
Azure IoT Edge Tools for Visual Studio 2017
Azure IoT Edge Tools for Visual Studio 2019

Install container engine

IoT Edge modules are packaged as containers, so you need a container engine on your development machine to build and manage the containers. We recommend using Docker Desktop for development because of its many features and popularity as a container engine. With Docker Desktop on a Windows computer, you can switch between Linux containers and Windows containers so that you can easily develop modules for different types of IoT Edge devices.

Use the Docker documentation to install on your development machine:

Set up Visual Studio and tools

The IoT extensions for Visual Studio help you to develop IoT Edge modules. These extensions provide project templates, automate the creation of the deployment manifest, and allow you to monitor and manage IoT Edge devices. In this section, you install Visual Studio and the IoT Edge extension, then set up your Azure account to manage IoT Hub resources from within Visual Studio.

This tutorial teaches the development steps for Visual Studio 2019. If you are using Visual Studio 2017 (version 15.7 or higher), the steps are similar. If you would rather use Visual Studio Code, refer to the instructions in Use Visual Studio Code to develop and debug modules for Azure IoT Edge.

  1. Prepare Visual Studio 2019 on your development machine.

    • If you don't already have Visual Studio on your development machine, Install Visual Studio 2019 with the following workloads:

      • Azure development
      • Desktop development with C++
      • .NET Core cross-platform development
    • If you do already have Visual Studio 2019 on your development machine, follow the steps in Modify Visual Studio to add the required workloads.

  2. Download and install the Azure IoT Edge Tools extension for Visual Studio 2019.

    If you are using Visual Studio 2017 (version 15.7 or higher), download and install the Azure IoT Edge Tools for Visual Studio 2017.

  3. When your installations are complete, open Visual Studio 2019 and select Continue without code.

  4. Select View > Cloud Explorer.

  5. Select the profile icon in the cloud explorer and sign in to your Azure account if you aren't signed in already.

  6. Once you sign in, your Azure subscriptions are listed. Expand the subscription that has your IoT hub.

  7. Under your subscription, expand IoT Hubs then your IoT hub. You should see a list of your IoT devices, and can use this explorer to manage them.

    Access IoT Hub resources in Cloud Explorer

Create a container registry

In this tutorial, you build a module and create a container image from the files. Then you push this image to a registry that stores and manages your images. Finally, you deploy your image from your registry to run on your IoT Edge device.

You can use any Docker-compatible registry to hold your container images. Two popular Docker registry services are Azure Container Registry and Docker Hub. This tutorial uses Azure Container Registry.

If you don't already have a container registry, follow these steps to create a new one in Azure:

  1. In the Azure portal, select Create a resource > Containers > Container Registry.

  2. Provide the following values to create your container registry:

    Field Value
    Subscription Select a subscription from the drop-down list.
    Resource group We recommend that you use the same resource group for all of the test resources that you create during the IoT Edge quickstarts and tutorials. For example, IoTEdgeResources.
    Registry name Provide a unique name.
    Location Choose a location close to you.
    SKU Select Basic.
  3. Select Create.

  4. Select your newly-created container registry from the Resources section of your Azure portal home page to open it.

  5. In the left pane of your container registry, select Access keys from the menu located under Settings.

  6. Enable Admin user with the toggle button and view the Username and Password for your container registry.

  7. Copy the values for Login server, Username, and password and save them somewhere convenient. You use these values throughout this tutorial to provide access to the container registry.

Create a new module project

The Azure IoT Edge Tools extension provides project templates for all supported IoT Edge module languages in Visual Studio. These templates have all the files and code that you need to deploy a working module to test IoT Edge, or give you a starting point to customize the template with your own business logic.

  1. Select File > New > Project...

  2. In the new project window, search for IoT Edge and choose the Azure IoT Edge (Windows amd64) project. Click Next.

    Create a new Azure IoT Edge project

  3. In the configure your new project window, rename the project and solution to something descriptive like CSharpTutorialApp. Click Create to create the project.

    Configure a new Azure IoT Edge project

  4. In the Add Module window, configure your project with the following values:

    Field Value
    Visual Studio Template Select C# Module.
    Module Name Accept the default IotEdgeModule1.
    Repository Url An image repository includes the name of your container registry and the name of your container image. Your container image is prepopulated from the module project name value. Replace localhost:5000 with the Login server value from your Azure container registry. You can retrieve the Login server value from the Overview page of your container registry in the Azure portal.

    The final image repository looks like <registry name>.azurecr.io/iotedgemodule1.

    Configure your project for target device, module type, and container registry

  5. Select Add to create the module.

Once your new project loads in the Visual Studio window, take a moment to familiarize yourself with the files that it created:

  • An IoT Edge project called CSharpTutorialApp.
    • The Modules folder contains pointers to the modules included in the project. In this case, it should be just IotEdgeModule1.
    • The hidden .env file holds the credentials to your container registry. These credentials are shared with your IoT Edge device so that it has access to pull the container images.
    • The deployment.template.json file is a template to help you create a deployment manifest. A deployment manifest is a file that defines exactly which modules you want deployed on a device, how they should be configured, and how they can communicate with each other and the cloud.


      In the registry credentials section, the address is autofilled from the information you provided when you created the solution. However, the username and password reference variables stored in the .env file. This is for security, as the .env file is git ignored, but the deployment template is not.

  • An IoT Edge module project called IotEdgeModule1.
    • The program.cs file contains the default C# module code that comes with the project template. The default module takes input from a source and passes it along to IoT Hub.
    • The module.json file hold details about the module, including the full image repository, image version, and which Dockerfile to use for each supported platform.

Set IoT Edge runtime version

The IoT Edge extension defaults to the latest stable version of the IoT Edge runtime when it creates your deployment assets.

Windows containers are only supported in the 1.1 long-term support version or the earlier 1.0 version. To develop modules for devices using Windows containers, update the IoT Edge runtime version in Visual Studio to match the IoT Edge version on those devices.

  1. In the Solution Explorer, right-click the name of your project and select Set IoT Edge runtime version.

    Right-click your project name and select set IoT Edge runtime version.

  2. Use the drop-down menu to choose the runtime version that your IoT Edge devices are running, then select OK to save your changes.

  3. Re-generate your deployment manifest with the new runtime version. Right-click the name of your project and select Generate deployment for IoT Edge.

Provide your registry credentials to the IoT Edge agent

The IoT Edge runtime needs your registry credentials to pull your container images onto the IoT Edge device. The IoT Edge extension tries to pull your container registry information from Azure and populate it in the deployment template.

  1. Open the deployment.template.json file in your module solution.

  2. Find the registryCredentials property in the $edgeAgent desired properties. It should have your registry address autofilled from the information you provided when creating the project, and then username and password fields should contain variable names. For example:

    "registryCredentials": {
      "<registry name>": {
        "username": "$CONTAINER_REGISTRY_USERNAME_<registry name>",
        "password": "$CONTAINER_REGISTRY_PASSWORD_<registry name>",
        "address": "<registry name>.azurecr.io"
  3. Open the .env file in your module solution. (It's hidden by default in the Solution Explorer, so you might need to select the Show All Files button to display it.)

  4. Add the Username and Password values that you copied from your Azure container registry.

  5. Save your changes to the .env file.


This tutorial uses admin login credentials for Azure Container Registry, which are convenient for development and test scenarios. When you're ready for production scenarios, we recommend a least-privilege authentication option like service principals. For more information, see Manage access to your container registry.

Review the sample code

The solution template that you created includes sample code for an IoT Edge module. This sample module simply receives messages and then passes them on. The pipeline functionality demonstrates an important concept in IoT Edge, which is how modules communicate with each other.

Each module can have multiple input and output queues declared in their code. The IoT Edge hub running on the device routes messages from the output of one module into the input of one or more modules. The specific code for declaring inputs and outputs varies between languages, but the concept is the same across all modules. For more information about routing between modules, see Declare routes.

The sample C# code that comes with the project template uses the ModuleClient Class from the IoT Hub SDK for .NET.

  1. In the program.cs file, find the SetInputMessageHandlerAsync method.

  2. The SetInputMessageHandlerAsync method sets up an input queue to receive incoming messages. Review this method and see how it initializes an input queue called input1.

    Find the input name in the SetInputMessageHandlserAsync constructor

  3. Next, find the SendEventAsync method.

  4. The SendEventAsync method processes received messages and sets up an output queue to pass them along. Review this method and see that it initializes an output queue called output1.

    Find the output name in the SendEventAsync constructor

  5. Open the deployment.template.json file.

  6. Find the modules property of the $edgeAgent desired properties.

    There should be two modules listed here. One is the SimulatedTemperatureSensor module, which is included in all the templates by default to provide simulated temperature data that you can use to test your modules. The other is the IotEdgeModule1 module that you created as part of this project.

    This modules property declares which modules should be included in the deployment to your device or devices.

  7. Find the routes property of the $edgeHub desired properties.

    One of the functions of the IoT Edge hub module is to route messages between all the modules in a deployment. Review the values in the routes property. One route, IotEdgeModule1ToIoTHub, uses a wildcard character (*) to include any message coming from any output queue in the IotEdgeModule1 module. These messages go into $upstream, which is a reserved name that indicates IoT Hub. The other route, sensorToIotEdgeModule1, takes messages coming from the SimulatedTemperatureSensor module and routes them to the input1 input queue of the IotEdgeModule1 module.

    Review routes in deployment.template.json

Build and push your solution

You've reviewed the module code and the deployment template to understand some key deployment concepts. Now, you're ready to build the IotEdgeModule1 container image and push it to your container registry. With the IoT tools extension for Visual Studio, this step also generates the deployment manifest based on the information in the template file and the module information from the solution files.

Sign in to Docker

Provide your container registry credentials to Docker on your development machine so that it can push your container image to be stored in the registry.

  1. Open PowerShell or a command prompt.

  2. Sign in to Docker with the Azure container registry credentials that you saved after creating the registry.

    docker login -u <ACR username> -p <ACR password> <ACR login server>

    You may receive a security warning recommending the use of --password-stdin. While that best practice is recommended for production scenarios, it's outside the scope of this tutorial. For more information, see the docker login reference.

Build and push

Your development machine now has access to your container registry, and your IoT Edge devices will too. It's time to turn the project code into a container image.

  1. Right-click the CSharpTutorialApp project folder and select Build and Push IoT Edge Modules.

    Build and push IoT Edge modules

    The build and push command starts three operations. First, it creates a new folder in the solution called config that holds the full deployment manifest, built out of information in the deployment template and other solution files. Second, it runs docker build to build the container image based on the appropriate dockerfile for your target architecture. Then, it runs docker push to push the image repository to your container registry.

    This process may take several minutes the first time, but is faster the next time that you run the commands.

  2. Open the deployment.windows-amd64.json file in the newly created config folder. (The config folder may not appear in the Solution Explorer in Visual Studio. If that's the case, select the Show all files icon in the Solution Explorer taskbar.)

  3. Find the image parameter of the IotEdgeModule1 section. Notice that the image contains the full image repository with the name, version, and architecture tag from the module.json file.

  4. Open the module.json file in the IotEdgeModule1 folder.

  5. Change the version number for the module image. (The version, not the $schema-version.) For example, increment the patch version number to 0.0.2 as though we had made a small fix in the module code.


    Module versions enable version control, and allow you to test changes on a small set of devices before deploying updates to production. If you don't increment the module version before building and pushing, then you overwrite the repository in your container registry.

  6. Save your changes to the module.json file.

  7. Right-click the CSharpTutorialApp project folder again, and select Build and Push IoT Edge Modules again.

  8. Open the deployment.windows-amd64.json file again. Notice that a new file wasn't created when you ran the build and push command again. Rather, the same file was updated to reflect the changes. The IotEdgeModule1 image now points to the 0.0.2 version of the container. This change in the deployment manifest is how you tell the IoT Edge device that there's a new version of a module to pull.

  9. To further verify what the build and push command did, go to the Azure portal and navigate to your container registry.

  10. In your container registry, select Repositories then iotedgemodule1. Verify that both versions of the image were pushed to the registry.

    View both image versions in container registry


If you encounter errors when building and pushing your module image, it often has to do with Docker configuration on your development machine. Use the following checks to review your configuration:

  • Did you run the docker login command using the credentials that you copied from your container registry? These credentials are different than the ones that you use to sign in to Azure.
  • Is your container repository correct? Does it have your correct container registry name and your correct module name? Open the module.json file in the IotEdgeModule1 folder to check. The repository value should look like <registry name>.azurecr.io/iotedgemodule1.
  • If you used a different name than IotEdgeModule1 for your module, is that name consistent throughout the solution?
  • Is your machine running the same type of containers that you're building? This tutorial is for Windows IoT Edge devices, so your Visual Studio files should have the windows-amd64 extension, and Docker Desktop should be running Windows containers.

Deploy modules to device

You verified that the built container images are stored in your container registry, so it's time to deploy them to a device. Make sure that your IoT Edge device is up and running.

  1. Open Cloud Explorer in Visual Studio and expand the details for your IoT hub.

  2. Select the name of the device that you want to deploy to. In the Actions list, select Create Deployment.

    Create deployment for single device

  3. In the file explorer, navigate to the config folder of your project and select the deployment.windows-amd64.json file. This file is often located at C:\Users\<username>\source\repos\CSharpTutorialApp\CSharpTutorialApp\config\deployment.windows-amd64.json

    Do not use the deployment.template.json file, which doesn't have the full module image values in it.

  4. Expand the details for your IoT Edge device in Cloud Explorer to see the modules on your device.

  5. Use the Refresh button to update the device status to see that the SimulatedTemperatureSensor and IotEdgeModule1 modules are deployed your device.

    View modules running on your IoT Edge device

View messages from device

The IotEdgeModule1 code receives messages through its input queue and passes them along through its output queue. The deployment manifest declared routes that passed messages from SimulatedTemperatureSensor to IotEdgeModule1, and then forwarded messages from IotEdgeModule1 to IoT Hub. The Azure IoT Edge tools for Visual Studio allow you to see messages as they arrive at IoT Hub from your individual devices.

  1. In Visual Studio Cloud Explorer, select the name of the IoT Edge device that you deployed to.

  2. In the Actions menu, select Start Monitoring Built-in Event Endpoint.

  3. Watch the Output section in Visual Studio to see messages arriving at your IoT hub.

    It may take a few minutes for both modules to start. The IoT Edge runtime needs to receive its new deployment manifest, pull down the module images from the container runtime, then start each new module.

    View incoming device to cloud messages

View changes on device

If you want to see what's happening on your device itself, use the commands in this section to inspect the IoT Edge runtime and modules running on your device.

The commands in this section are for your IoT Edge device, not your development machine. If you're using a virtual machine for your IoT Edge device, connect to it now. In Azure, go to the virtual machine's overview page and select Connect to access the remote desktop connection. On the device, open a command or PowerShell window to run the iotedge commands.

  • View all modules deployed to your device, and check their status:

    iotedge list

    You should see four modules: the two IoT Edge runtime modules, SimulatedTemperatureSensor, and IotEdgeModule1. All four should be listed as running.

  • Inspect the logs for a specific module:

    iotedge logs <module name>

    IoT Edge modules are case-sensitive.

    The SimulatedTemperatureSensor and IotEdgeModule1 logs should show the messages they're processing. The edgeAgent module is responsible for starting the other modules, so its logs will have information about implementing the deployment manifest. If any module isn't listed or isn't running, the edgeAgent logs will probably have the errors. The edgeHub module is responsible for communications between the modules and IoT Hub. If the modules are up and running, but the messages aren't arriving at your IoT hub, the edgeHub logs will probably have the errors.

Clean up resources

If you plan to continue to the next recommended article, you can keep the resources and configurations that you created and reuse them. You can also keep using the same IoT Edge device as a test device.

Otherwise, you can delete the local configurations and the Azure resources that you used in this article to avoid charges.

Delete Azure resources

Deleting Azure resources and resource groups is irreversible. Make sure that you don't accidentally delete the wrong resource group or resources. If you created the IoT hub inside an existing resource group that has resources that you want to keep, delete only the IoT hub resource itself, not the resource group.

To delete the resources:

  1. Sign in to the Azure portal, and then select Resource groups.

  2. Select the name of the resource group that contains your IoT Edge test resources.

  3. Review the list of resources that are contained in your resource group. If you want to delete all of them, you can select Delete resource group. If you want to delete only some of them, you can click into each resource to delete them individually.

Next steps

In this tutorial, you set up Visual Studio 2019 on your development machine and deployed your first IoT Edge module from it. Now that you know the basic concepts, try adding functionality to a module so that it can analyze the data passing through it. Choose your preferred language:

C C#