Tutorial: Develop IoT Edge modules with Linux containers

Applies to: yes icon IoT Edge 1.1 yes icon IoT Edge 1.2

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

In the quickstart, you created an IoT Edge device and deployed a module from the Azure Marketplace. This tutorial walks through developing and deploying your own code to an IoT Edge device. This article 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 Linux device. This example was chosen because it's the most common developer scenario for IoT Edge solutions. Even if you plan on using a different language or deploying an Azure service, this tutorial is still useful to learn about the development tools and concepts. Complete this introduction to the development process, then 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 Code 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:

  • 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.
  • Most operating systems that can run a container engine can be used to develop IoT Edge modules for Linux devices. This tutorial uses a Windows computer, but points out known differences on macOS or Linux.
  • Install Git, to pull module template packages later in this tutorial.
  • C# for Visual Studio Code (powered by OmniSharp) extension.
  • .NET Core 2.1 SDK.

An Azure IoT Edge device:

  • We recommend that you don't run IoT Edge on your development machine, but instead use a separate device. This distinction between development machine and IoT Edge device more accurately mirrors a true deployment scenario, and helps to keep the different concepts straight.
  • If you don't have a second device available, use the quickstart article to create an IoT Edge device in Azure with a Linux virtual machine.

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 with executable code. You can deploy one or more modules to an IoT Edge device. Modules perform specific tasks like ingesting data from sensors, cleaning and analyzing data, 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 example, the most common scenario is someone developing a module on a Windows computer intending to target a Linux device running IoT Edge. In that case, the container operating system would be Linux. As you go through this tutorial, keep in mind the difference between the development machine OS and the container OS.


If you're using IoT Edge for Linux on Windows, then the target device in your scenario is the Linux virtual machine, not the Windows host.

This tutorial targets devices running IoT Edge with Linux containers. You can use your preferred operating system as long as your development machine runs Linux containers. We recommend using Visual Studio Code to develop with Linux containers, so that's what this tutorial will use. You can use Visual Studio as well, although there are differences in support between the two tools.

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

Visual Studio Code Visual Studio 2017/2019
Linux device architecture Linux AMD64
Linux ARM32
Linux AMD64
Linux ARM32
Azure services Azure Functions
Azure Stream Analytics
Azure Machine Learning
Languages C
More information Azure IoT Edge for Visual Studio Code Azure IoT Edge Tools for Visual Studio 2017
Azure IoT Edge Tools for Visual Studio 2019


Support for Linux ARM64 devices is available in public preview. For more information, see Develop and debug ARM64 IoT Edge modules in Visual Studio Code (preview).

This tutorial teaches the development steps for Visual Studio Code. If you would rather use Visual Studio, refer to the instructions in Use Visual Studio 2019 to develop and debug modules for Azure IoT Edge.

Install container engine

IoT Edge modules are packaged as containers, so you need a container engine on your development machine to build and manage them. We recommend Docker Desktop for development because of its feature support and popularity. Docker Desktop on Windows lets you 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 VS Code and tools

Use the IoT extensions for Visual Studio Code 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 Code and the IoT extension, then set up your Azure account to manage IoT Hub resources from within Visual Studio Code.

  1. Install Visual Studio Code on your development machine.

  2. Once the installation is finished, select View > Extensions.

  3. Search for Azure IoT Tools, which is actually a collection of extensions that help you interact with IoT Hub and IoT devices, as well as developing IoT Edge modules.

  4. Select Install. Each included extension installs individually.

  5. When the extensions are done installing, open the command palette by selecting View > Command Palette.

  6. In the command palette, search for and select Azure: Sign in. Follow the prompts to sign in to your Azure account.

  7. In the command palette again, search for and select Azure IoT Hub: Select IoT Hub. Follow the prompts to select your Azure subscription and IoT hub.

  8. Open the explorer section of Visual Studio Code by either selecting the icon in the activity bar on the left, or by selecting View > Explorer.

  9. At the bottom of the explorer section, expand the collapsed Azure IoT Hub / Devices menu. You should see the devices and IoT Edge devices associated with the IoT hub that you selected through the command palette.

    View devices in your IoT hub

Create a container registry

In this tutorial, you use the Azure IoT Tools extension to 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. After your container registry is created, browse to it, and from the left pane select Access keys from the menu located under Settings.

  5. Click to Enable Admin user to view the Username and Password for your container registry.

  6. 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.

    Copy login server, username, and password for container registry

Create a new module project

The Azure IoT Tools extension provides project templates for all supported IoT Edge module languages in Visual Studio Code. 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.

For this tutorial, we use the C# module template because it is the most commonly used template.

Create a project template

In the Visual Studio Code command palette, search for and select Azure IoT Edge: New IoT Edge Solution. Follow the prompts and use the following values to create your solution:

Field Value
Select folder Choose the location on your development machine for VS Code to create the solution files.
Provide a solution name Enter a descriptive name for your solution or accept the default EdgeSolution.
Select module template Choose C# Module.
Provide a module name Accept the default SampleModule.
Provide Docker image repository for the module An image repository includes the name of your container registry and the name of your container image. Your container image is prepopulated from the name you provided in the last step. 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/samplemodule.

Provide Docker image repository

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

  • The .vscode folder contains a file called launch.json, which is used for debugging modules.

  • The modules folder contains a folder for each module in your solution. Right now, that should only be SampleModule, or whatever name you gave to the module. The SampleModule folder contains the main program code, the module metadata, and several Docker files.

  • The .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.debug.template.json file and deployment.template.json file are templates that 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. The template files use pointers for some values. When you transform the template into a true deployment manifest, the pointers are replaced with values taken from other solution files. Locate the two common placeholders in your deployment template:

    • 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 the variables stored in the .env file. This configuration is for security, as the .env file is git ignored, but the deployment template is not.
    • In the SampleModule section, the container image isn't filled in even though you provided the image repository when you created the solution. This placeholder points to the module.json file inside the SampleModule folder. If you go to that file, you'll see that the image field does contain the repository, but also a tag value that is made up of the version and the platform of the container. You can iterate the version manually as part of your development cycle, and you select the container platform using a switcher that we introduce later in this section.

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. Currently, the latest stable version is version 1.2. If you're developing modules for devices running the 1.1 long-term support version or the earlier 1.0 version, update the IoT Edge runtime version in Visual Studio Code to match.

  1. Select View > Command Palette.

  2. In the command palette, enter and run the command Azure IoT Edge: Set default IoT Edge runtime version.

  3. Choose the runtime version that your IoT Edge devices are running from the list.

After selecting a new runtime version, your deployment manifest is dynamically updated to reflect the change to the runtime module images.

Provide your registry credentials to the IoT Edge agent

The environment file stores the credentials for your container registry and shares them with the IoT Edge runtime. The runtime needs these credentials to pull your container images onto the IoT Edge device.


If you didn't replace the localhost:5000 value with the login server value from your Azure container registry, in the Create a project template step, the .env file and the registryCredentials section of the deployment manifest will be missing.

The IoT Edge extension tries to pull your container registry credentials from Azure and populate them in the environment file. Check to see if your credentials are already included. If not, add them now:

  1. Open the .env file in your module solution.
  2. Add the username and password values that you copied from your Azure container registry.
  3. 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 or repository-scoped tokens. For more information, see Manage access to your container registry.

Select your target architecture

Currently, Visual Studio Code can develop C# modules for Linux AMD64 and ARM32v7 devices. You need to select which architecture you're targeting with each solution, because that affects how the container is built and runs. The default is Linux AMD64.

  1. Open the command palette and search for Azure IoT Edge: Set Default Target Platform for Edge Solution, or select the shortcut icon in the side bar at the bottom of the window.

    Select architecture icon in side bar

  2. In the command palette, select the target architecture from the list of options. For this tutorial, we're using an Ubuntu virtual machine as the IoT Edge device, so will keep the default amd64.

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. Open the Program.cs file, which is inside the modules/SampleModule/ folder.

  2. In program.cs, find the SetInputMessageHandlerAsync method.

  3. 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 SetInputMessageCallback constructor

  4. Next, find the SendEventAsync method.

  5. 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 SendEventToOutputAsync

  6. Open the deployment.template.json file.

  7. 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 SampleModule module that you created as part of this solution.

  8. At the bottom of the file, find the desired properties for the $edgeHub module.

    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, SampleModuleToIoTHub, uses a wildcard character (*) to indicate any messages coming from any output queues in the SampleModule module. These messages go into $upstream, which is a reserved name that indicates IoT Hub. The other route, sensorToSampleModule, takes messages coming from the SimulatedTemperatureSensor module and routes them to the input1 input queue that you saw initialized in the SampleModule code.

    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 SampleModule container image and push it to your container registry. With the IoT tools extension for Visual Studio Code, 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 so that it can push your container image to be stored in the registry.

  1. Open the Visual Studio Code integrated terminal by selecting View > Terminal.

  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.

  3. Log in to Azure Container Registry

    az acr login -n <ACR registry name>

Build and push

Visual Studio Code now has access to your container registry, so it's time to turn the solution code into a container image.

  1. In the Visual Studio Code explorer, right-click the deployment.template.json file and select Build and Push IoT Edge Solution.

    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.amd64.json file in newly created config folder. The filename reflects the target architecture, so it will be different if you chose a different architecture.

  3. Notice that the two parameters that had placeholders now are filled in with their proper values. The registryCredentials section has your registry username and password pulled from the .env file. The SampleModule has the full image repository with the name, version, and architecture tag from the module.json file.

  4. Open the module.json file in the SampleModule 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 deployment.template.json file again, and again select Build and Push IoT Edge Solution.

  8. Open the deployment.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 SampleModule image now points to the 0.0.2 version of the container.

  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 samplemodule. 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 SampleModule folder to check. The repository value should look like <registry name>.azurecr.io/samplemodule.
  • If you used a different name than SampleModule 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 Linux IoT Edge devices, so Visual Studio Code should say amd64 or arm32v7 in the side bar, and Docker Desktop should be running Linux 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. In the Visual Studio Code explorer, under the Azure IoT Hub section, expand Devices to see your list of IoT devices.

  2. Right-click the IoT Edge device that you want to deploy to, then select Create Deployment for Single Device.

    Create deployment for single device

  3. In the file explorer, navigate into the config folder then select the deployment.amd64.json file.

    Do not use the deployment.template.json file, which doesn't have the container registry credentials or module image values in it. If you're targeting a Linux ARM32 device, the deployment manifest will be named deployment.arm32v7.json.

  4. Under your device, expand Modules to see a list of deployed and running modules. Click the refresh button. You should see the new SimulatedTemperatureSensor and SampleModule modules running on your device.

    It may take a few minutes for the 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 modules running on your IoT Edge device

View messages from device

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

  1. In the Visual Studio Code explorer, right-click the IoT Edge device that you want to monitor, then select Start Monitoring Built-in Event Endpoint.

  2. Watch the output window in Visual Studio Code to see messages arriving at your IoT hub.

    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 secure shell connection.

  • 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 SampleModule. 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 SampleModule 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 Code 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: