27 June 2019

Deploying .NET Core Application to Azure Kubernetes Cluster for Less Than 5$


Microservice architecture and Docker are becoming more and more popular these days. And there are many reasons for that, as it makes running apps locally and on production easier and more efficient.

In this article, I will shortly brief you through some main terminologies and show you how to:

  • Dockerise an ASP .NET Core application.
  • Create and run Docker image.
  • Use this image in your local Kubernetes cluster.
  • Create Azure Container Register and push your image there.
  • Setup Azure Kubernetes Service and connect it to ACR.
  • Scale you cluster and change number of replicas.
  • And the most important: how to clean all this mess.

What do you need to know about Docker and Kubernetes?

Microservices, also known as microservice architecture, are being used to split an application into small, single responsible services so they can be developed and scaled separately instead of one, big monolith application.

Container, on the other hand, is a nano-server which we can use as a host for an app instead of virtual machines. To store containerised apps’ images, we can use container register. So how do we create, deploy, and run applications using containers? One way to do this is by using Docker, an open-sourced online tool.

Another thing we need, is a tool for managing containerised workloads and services. In this case, we’ll use Kubernetes, as it facilitates both declarative configuration and automation. By default, Docker uses Docked Swarm for an orchestration but in a new version of Docker Desktop, available for both MacOS and Windows, you can change this to use Kubernetes.


To follow the example explained in this article, you will need a computer running on macOS or Windows. You can use Linux, too, but I won’t discuss this in this example.

Then, if you’re a Windows user, enable Hyper-V on your machine if you haven’t done it yet.

The next thing to do is to install or update Docker Desktop and Azure CLI, which you will need a little bit later.

Run the app

In the next few steps we will try four different approaches to run the app:

  • locally,
  • via local Docker instance,
  • via your local Kubernetes instance,
  • via Kubernetes on Azure.

Let’s get started!

It will be much simpler to have the same codebase so open your PowerShell and clone my repository.

Here, you can find the simplest possible core application which should display a message from appsettings.json and your machine name. If you have installed .NET Core Runtime, you can build and run the app locally without Docker:

  • Navigate to project folder SHKube.
  • Run the application dotnet run.

Application is listening on port 5000 so open your browser and navigate to http://localhost:5000. You should see the name of our machine:

You should see the name of our machine:

localhost 5000 screen name

Now, it’s time to dockerise our app

There are at least two ways to build and run an app inside Docker: via command line only or by using Dockerfile. I think that the second approach is more clear, so I will use it in this example. Take a look at the Dockerfile inside the project’s folder.

 FROM AS base  WORKDIR /app    FROM AS build  WORKDIR /src  COPY ["*.csproj", "./"] 
      RUN dotnet restore  COPY . ./  WORKDIR /src  RUN dotnet build  -c Release -o
      /app    FROM build AS publish  RUN dotnet publish -c Release -o /app    FROM base AS
      final  WORKDIR /app  COPY --from=publish /app .  ENTRYPOINT ["dotnet", "SHKube.dll"] 

What we have here is a multi-stage build. We are using two images from DockerHub.

  • is used to build an app – it contains .NET Core SDK which is necessary to build our app
  • which is lighter and contains only .NET Core Runtime to run our app

Let’s build a new image with our app.

 docker build
      . -t shkube:local 

We have named the image shkube and tagged it local. Check out if the image is on a list of local images:

 docker image list | select
      -First 2 

You can also use docker image list only to see all images. Finally, we can run this image:

 docker run -d -p
      5000:80 shkube:local 
  • -d means detached mode – run in a background and print container ID
  • -p binds port 80 of a container to port 5000 of a host

Open the browser and navigate to http://localhost:5000. You should now see the ID of a container.

localhost5000 screen

Now, usedocker stop to stop and docker rm to remove the container. You can also use:

 docker container rm -f
      $(docker ps -aq) 

to clean everything.

We can now move to Kubernetes

Open Docker settings and open Kubernetes tab. Select two checkboxes (see the screen below) and wait for an installation to finish. It should look very similar for macOS users.

Kubernetes tab screen

As with the Dockerfile, you can use yml file to have all configuration necessary for Kubernetes in one place.

Open shkubedeploy.yml

 apiVersion: apps/v1 
      kind: Deployment  metadata:    name: shkube-deployment    labels: 
          app: shkube  spec:    replicas: 1    template: 
          metadata:        name: shkube 
            labels:          app: shkube 
          spec:        containers:        -
      name: shkube          image: shkube:local 
              imagePullPolicy: IfNotPresent 
            restartPolicy: Always    selector: 
          matchLabels:        app: shkube     
      ---    apiVersion: v1  kind: Service  metadata:    name: shkube-service 
      spec:    selector:      app: shkube    ports: 
          - port: 80    type: NodePort 

You have just created a new Deployment for Kubernetes. It contains one service named shkube-service, which exposesport 80 and contains one instancereplicas: 1 of our container image shkube:local.

Containers are ephemeral. It could just die. Kubernetes will run a new instance of our container if it happens. It is recommended to have at least three instances. We will scale it up a later.

Now we must run our deployment:

 kubectl create -f

And check out if the service is still pending or not:

 kubectl get svc
screen code 1

Grab a port number and open your browser. In my case it is http://localhost:31192/ . Now you should see the name of the deployment.

deployment name

Let’s clean this mess:

 kubectl delete -f

Simple as that!

Finally, welcome to Azure!

With Azure things are a little bit more complicated but don’t worry – you can handle this! You need to follow a few additional steps before you can continue with the deployment:

  1. Create an Azure Resource Group to have everything in one place.
  2. Create Azure Container Register (ACR) to push and share our local image.
  3. Add Azure Service Principal to be able to use our ACR with Azure Kubernetes Service (AKS).
  4. Create AKS.
  5. And then, spin out the deployment.

Log in to Azure from PowerShell.

 az login 

Create a Resource Group

 az group create
      -n shkuberg -l westeurope 
  • -n stands for name
  • -l stands for region (I have picked up Western Europe because it’s the closest one, but it doesn’t matter in this case)

Next, create Azure Container Register.

 az acr create
      -n shkubeacr -g shkuberg --sku standard 
  • -g stands for resource group
  • --sku stands for Stock Keeping Unit (available options: Basic, Classic, Premium, Standard)

Find loginServer, we will need that in a moment (mine is

Check out if the ACR is ready:

 az acr list -o

And log in to ACR:

 az acr login
      -n shkubeacr 

Now, let’s tag our existing image to match the ACR name. Use loginServer that you have stored before:

      tag shkube:local 

Check out all local images:

 docker image list 

You can now see two images with the same ID but different tags. Push an image to the ACR:

 docker push 

And check if it is there:

 az acr repository
      list -n shkubeacr -o table 

Good job!

Add Azure Service Principal

Create a service principal and configure its access to Azure resources.

 az ad sp create-for-rbac --skip-assignment 

Remember the appId and password? -You need those now. Mine are:

      "cfed7ab3-8db9-4bba-86c9-2311d505d2ad",  "password": "7193c36f-3123-4453-8ee7-2aa7bf071007", 

Create a new role assignment (use your appId after --assignee):

 $acrId = az acr show
      --name shkubeacr --resource-group shkuberg --query "id" --output tsv   
      az role assignment create --assignee "cfed7ab3-8db9-4bba-86c9-2311d505d2ad" --role Reader --scope

And you are done with permissions!

Azure Kubernetes Service

 az aks create
      `   --name shkubeakscluster `   --resource-group shkuberg ` 
       --node-count 1 `   --generate-ssh-keys `   --service-principal
      "cfed7ab3-8db9-4bba-86c9-2311d505d2ad" `   --client-secret "7193c36f-3123-4453-8ee7-2aa7bf071007" 

Use appId for service-principal and password for client-secret.

It’s a good moment for a short break, so grab yourself a cup of coffee and wait.

You have just created a single, brand new ASK.

Check where your kube context point to.

      C:\Users\{yourLocalUserName}\.kube\config | more 
screen code 2

You should see the localhost here, but you have to add the one from Azure.

 az aks get-credentials --name shkubeakscluster --resource-group shkuberg 

Check again:

      C:\Users\{yourLocalUserName}\.kube\config | sls "shkubeakscluster" 

Let’s check a list of nodes:

 kubectl get

And grab ACR Login server to update our shkubedeploy.yml file.

 az acr list
      --resource-group shkuberg --query "[].{acrLoginServer:loginServer}" --output table 

Openshkubedeploy.yml file and change image to the one from ACR instead of local image and change the service spec type to LoadBalancer instead of NodePort because we are on Azure now.

code example azure 1

And you are ready to go. Upload your config file and wait for it to be ready.

 kubectl apply -f
      .\shkubedeploy.yml  kubectl get service --watch 
screen code 3

Copy an IP and check it out in a browser.

deployment name

We can now increase a number of Kubernetes clusters:

 kubectl get nodes 
      az aks scale --name shkubeakscluster --resource-group shkuberg ` 
       --node-count 3  kubectl get nodes 

Or change the number of replicas:

 kubectl get
      deployment  kubectl scale --replicas=5 deployment/shkube-deployment  kubectl get

How to update our application now?

Let’s change a Message in appsettings.json to **Hello azure: “. As before, we have to build a new image and push it to the register.

 docker build . -t  docker push 

We have to update our deployment file to point to the new image and change the number of replicas to have the same value as it is on Azure now. Open and edit shkubedeploy.yml:

code example azure

And apply all changes.

 kubectl apply -f

Refresh your browser. Now, you should see a new message!

hello Azure message

This is it! You are ready to clean all the mess you have made:

1. Remove a deployment

 kubectl delete -f

2. Remove Kubernetes clusters

 az aks delete
      --name shkubeakscluster --resource-group shkuberg 

3. Remove container register

 az acr delete
      -n shkubeacr 

4. Remove resource group

 az group delete
      --name shkuberg 

5. And switch back to your local Kubernetes context

 kubectl config
      use-context docker-for-desktop  kubectl config unset contexts.shkubeakscluster 


Do you remember the times when your app was working well locally, but not on production and how hard it was to debug? And this strange behaviour when your single-node was scaled horizontally and you couldn’t figure out what was wrong?

We went a long journey to prove that now it can be done better.

  • We have run the same ASP.NET Core application locally and inside a Docker container.
  • We have used the same Docker container image with and without Kubernetes.
  • We have used the same Kubernetes deployment configuration on our workstation and then on Azure.

And we have made it for less than 5$!

Szczepan Błaszkiewicz
Software Developer

I’m a .NET-driven full stack developer. I love the idea behind Docker and I’m happy to share it.