.NET Core Workers in Azure Container Instances

Brady Gaster

.NET Core Workers in Azure Container Instances

In .NET Core 3.0 we are introducing a new type of application template called Worker Service. This template is intended to give you a starting point for writing long running services in .NET Core. In this walkthrough you’ll learn how to use a Worker with Azure Container Registry and Azure Container Instances to get your Worker running as a microservice in the cloud.

Since the Worker template Glenn blogged about is also available via the dotnet new command line, I can create one on my Mac and edit the code using Visual Studio for Mac or Visual Studio Code (which I’ll be using here to take advantage of the integrated Docker extension).

dotnet new worker

I’ll use the default from the Worker template. As it will write to logs during execution via ILogger, I’ll be able to tell quickly from looking in the logs if the Worker is running.

public class Worker : BackgroundService
    private readonly ILogger<Worker> _logger;

    public Worker(ILogger<Worker> logger)
        _logger = logger;

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        while (!stoppingToken.IsCancellationRequested)
            _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
            await Task.Delay(1000, stoppingToken);

Visual Studio Code’s Docker tools are intelligent enough to figure out this is a .NET Core app, and will suggest the correct Docker file via the Command Palette’s Add Docker files to workspace option.

By right-clicking the resulting Dockerfile I can build the Worker into a Docker image in one click.

The Build Image option will package my Worker’s code into a Docker container locally. The second option, ACR Tasks: Build Image would use Azure Container Registry Tasks to build the image in the cloud, rather than on disk. This is helpful for scenarios when the base image is larger than I want to download locally or when I’m building an application on a Windows base image from Linux or Mac. You can learn more about ACR Tasks in the ACR docs. The Azure CLI makes it easy to login to the Azure Container Registry using the Azure CLI. This results in my Docker client being authenticated to the Azure Container Registry in my subscription.

az acr login -n BackgroundWorkerImages

This can be done in the VS Code integrated terminal or in the local terminal, as the setting will be persisted across the terminals’ environment. It can’t be done using the cloud shell, since logging into the Azure Container Registry requires local shell access so local Docker images can be accessed. Before I push the container image into my registry, I need to tag the image with the URI of the image once it has been pushed into my registry. I can easily get the ACR instance URI from the portal.

I’ll copy the URI of the registry’s login server in the portal so I can paste it when I tag the image later.

By selecting the backgroundworker:latest image in Visual Studio Code’s Docker explorer pane, I can select Tag Image.

I’ll be prompted for the tag, and I can easily paste in the URI I copied from the portal.

Finally, I can right-click the image tag I created and select Push, and the image will be pushed into the registry. Once I have a Docker image in the registry, I can use the CLI or tools to deploy it to Azure Container Instances, Kubernetes, or even Azure App Service.

Now that the worker is containerized and stored in the registry, starting an instance of it is one click away.

Once the container instance starts up, I’ll see some logs indicating the worker is executing, but these are just the basic startup logs and not my information-level logs I have in my Worker code.

Since I added Information-level logs during the worker’s execution, the configuration in appsettings.json (or the environment variable for the container instance) will need to be updated to see more verbose logs.

  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.Hosting.Lifetime": "Information"

Once the code is re-packaged into an updated Docker image and pushed into the Azure Container Registry, following a simple Restart…

… more details will be visible in the container instance’s logging output.

The Worker template makes it easy to create long-running background workers that you can run for as long as you need in Azure Container Instances. New container instances can be created using the portal or the Azure Command Line. Or, you can opt for more advanced scenarios using Azure DevOps or Logic Apps. With the Worker template making it easy to get started building microservices using your favorite ASP.NET Core idioms and Azure’s arsenal of container orchestration services you can get your microservices up and running in minutes.


Discussion is closed. Login to edit/delete existing comments.

  • Ben Hayat 0

    Brady, I didn’t get to read the whole article, but my question is, is the worker available if we are hosting our app on a dedicated server and NOT Azure?

    Secondly, what’s the difference between Worker service and the product called “HangFire”? Do they both accomplish the same thing? If yes, is it better to use yours for long scheduling jobs that is is running 24/7?


    • Brady GasterMicrosoft employee 0

      Yes, Ben, you can totally use the Worker template “outside of Azure.” Glenn wrote a fantastic blog last week on a key non-cloud scenario for the Worker project – you can use these to build Windows services, too. So they’re “not just for cloud,” but they sure do make microservice development easier, so they’re great for cloud-native scenarios. Keep watching the blog, we have a few other use-cases on the horizon we’ll be showing off for the Worker. 

    • Brady GasterMicrosoft employee 0

      Pardon me, as I missed the second question – I’m unfamiliar with HangFire. I’ll mention HangFire to Glenn and investigate it myself. But unless Glenn is aware of it, the Worker and HangFire are mutually exclusive concepts. 

    • David FowlerMicrosoft employee 0

      Hangfire existed long before the generic host and the worker template and is a valid alternative. It’s much more feature rich. If anything I’d expect a future where more frameworks like hangfire re-plat on top of the generic host. The generic host is trying to be a low level building block for higher level frameworks like hangfire.

      • Ben Hayat 0


        Thanks David for clear answer.

        • Brady GasterMicrosoft employee 0

          Totally agree with David after reviewing HangFire. I see so much potential for these two things to work well together. 

  • Vikram DadwalMicrosoft employee 0

    Hi, thanks for this article .

    I have one question though, when i read the ACI document, it was mentioned that it isbideal for scenarios where you run the applixation for some time and then shutdown the container as ACI charges per second. Then why someone will creates a always running worker in ACI ?

    • Brady GasterMicrosoft employee 0

      You’re right in that ACI charges per-second, so it isn’t typically thought of as a host for forever-running containers. That said, the price of ACI instances have been reduced significantly so for some workloads it may be more attractive now. ACI is that it works great for not only testing one’s containers out, but for scenarios in which you have some other “thing” – like scheduled CLI execution or a Logic App running on a recurrence – that would stop and start your instances. 

      ACI billing begins at the time an image is pulled and runs until the container group is stopped, so one should take care to explicitly stop the container group – simply stopping/terminating the containers in the group isn’t a complete shutdown of an ACI container group. 

      We’ll have other articles on this site about options for running Workers – we’ve shown how to use them as Windows services and in container instances, but we’ll have a few follow-up articles that will point out additional scenarios, like Kubernetes and deployment techniques using Azure DevOps. Kubernetes would arguably be a better place for an infinitely-running containerized Worker, but for those non-inifinite scenarios ACI is a convenient option. 

      • Vikram DadwalMicrosoft employee 0

        Thanks Brady for explaining well.
        Btw SF Mesh is also a good place for containers.  

  • King David Consulting LLC 0

    HangFire provides with the ability to schedule the tasks with chron pattern. There is an alternative https://github.com/kdcllc/CronScheduler.AspNetCore.

  • Ankit Kumar 0

    Hey, Great article, had been looking for windows background service like implementation.However what difference would it make if I just write my service like a console application and run it on docker? 

    • Brady GasterMicrosoft employee 0

      Did you see Glenn’s article on using the Worker for Windows services? 

  • raymond.wong@hl-insurance.com 0

    Hello Brady. Can I deploy the workers to Azure web apps instead of Container service? What is the difference between them?

    • Brady GasterMicrosoft employee 0

      You can push them to App Service. If you install the WebJobs SDK NuGet, they’ll even show up as WebJobs. πŸ™‚ 

      • Tory Berra 0

        Going forward does that mean the default way to write a WebJob in Core is to use BackgroundService via dotnet new worker?

        • Brady GasterMicrosoft employee 0

          That is indeed one way. My favorite way, personally, but yes. The VS publishing tools will publish a Worker to App Service as a WebJob. 

  • Jeevanatham Subburaj 0

    Brady, With new SDK Worker template in csproj, i am getting the following error when i build the docker image. 
    error MSB4236: The SDK ‘Microsoft.NET.Sdk.Worker’ specified could not be found.

    FROM microsoft/dotnet:3.0-runtime AS base
    FROM microsoft/dotnet:3.0-sdk AS build
    Any help would be appreciated.

    • Brady GasterMicrosoft employee 0

      Sorry about that – it’s the nature of the beast that our SDKs are a little ahead of our Docker image publishes. I’m happy to see you putting it through the loop – apologies for the //build/-caused delay in response. 

      Change your Dockerfile to match this below: 

      FROM mcr.microsoft.com/dotnet/core/sdk:3.0 AS baseWORKDIR /app
      FROM mcr.microsoft.com/dotnet/core/runtime:3.0 AS buildWORKDIR /src

      • Jeevanatham Subburaj 0

        Thanks Brady. That worked. πŸ™‚ I actually used runtime image as base and SDK image for build. 

  • leszekpietrzak@fluentmoney.co.uk 0

    Thank you very much for that article.

    Is is good to use this WorkerService in docker container as RabbitMQ consumer on Azure Containers ?
    I am not familiar with Azure, could you give a tip?

    Best regards

  • Sunil Shrestha 0

    Hi Brady, good article. How do I configure healthcheck endpoint for worker service in container so that Kubernetes/Swarm can know if it is down?

  • Parimal Shah 0

    I want to implement CI CD on worker service and have to deploy worker service on AKS. What steps should I follow in CI CD?

Feedback usabilla icon