Continuous deployment of Windows containers with CircleCI and AKS

Posted by

This post has been republished via RSS; it originally appeared at: Microsoft Tech Community - Latest Blogs - .

This blog post has been co-authored by Microsoft and Jacob Schmitt from CircleCI


Containerization adds portability, scalability, and consistency to software delivery, allowing development teams to efficiently deploy complex workloads in the cloud. Modernizing legacy applications through containerization has become a critical process for enterprises that want to achieve greater agility and responsiveness in adapting to market changes and customer demands.


To reduce the complexity of managing containerized applications at scale, teams can use a robust continuous integration and delivery (CI/CD) platform like CircleCI to build, test, and deploy to managed orchestration platforms such as Azure Kubernetes Service (AKS). CircleCI is a shared CI/CD platform that supports over 2 million developers running more than 90 million jobs per month. With native Windows support for cloud or self-hosted jobs and easy-to-use integrations with AKS, Azure Container Registry (ACR), and thousands of other third-party tools and services, CircleCI offers a fast, flexible, and scalable platform for building containerized Windows applications.


In this tutorial, you will learn how to set up a CI/CD pipeline to build a Dockerized ASP.NET application and deploy to an Azure Kubernetes Service cluster. You will use CircleCI orbs, which are prepacked snippets of YAML configuration, to easily integrate your pipeline with AKS. With this solution, you will be able to efficiently build, validate, and ship containerized Windows applications without manual intervention, eliminating development bottlenecks for faster, more reliable software delivery.


Why CircleCI

CircleCI is a leading continuous integration and delivery platform for software innovation at scale. With intelligent automation and delivery tools, CircleCI is used by the world's best engineering teams to radically reduce the time from idea to execution. Accelerate your software delivery with powerful compute options and easy integrations while maintaining deep visibility and control for secure and compliant processes.

Whether you’re starting from our generous free plan or taking advantage of the most advanced features on our Scale plan, CircleCI is built to grow alongside you as your business needs evolve. With premium 24/7 support, you get access to personalized onboarding assistance, regular optimization reviews, and on-demand customer support for an uninterrupted flow of value. Add CircleCI’s best-in-class CI/CD to your toolchain to streamline development, boost productivity, and drive better business outcomes for your organization.



  • Familiarity with Docker and Docker Desktop installed on your local machine. You can follow Docker’s tutorial for Windows or macOS.
  • A GitHub account
  • A CircleCI account 
  • An Azure account with an active subscription
  • Basic knowledge of building applications with ASP.NET Core framework
  • Azure CLI installed on your workstation
  • An Azure account

Environment setup

To help demonstrate the concepts in this article, you will use a basic .NET application — a weather API that gives a naive forecast for the week. Use git to clone the sample application to your development environment:

git clone

Navigate into the cloned directory:

cd docker-dotnet-app-aks


Authenticating with Azure

To run commands via Azure CLI, you will need to be authenticated. Do that with the following command:

az login

This opens a new window in your browser. Provide your email address and password to complete the authentication process. Once the authentication process is completed, the subscription details will be printed out to the terminal. Make a special note of the `id` key, as it will be used when creating a service principal.


If you haven’t already done so, create a new service principal using the following command:

az ad sp create-for-rbac --name <service_principal_name> --scopes /subscriptions/<subscription_id> --role owner

In the command, `<service_principal_name>` can be any name you choose, and `<subscription_id>` is the value of the `id` key in the terminal output of the successful login. On successful completion, the service principal information will be printed to the terminal. The information



Setting up the container registry

Create a new resource group using the following command:

az group create --name AzureRG --location eastus

Next, create a new container registry using the following command:

az acr create --resource-group AzureRG --name dotnetaksdemo --sku Basic

Note: The registry name has to be unique. If you get a message such as `The registry DNS name is already in use.`, you will need to choose a different, unique name. You can also use the Registries API to check for available names. However, for the rest of the article, the registry name will be referred to as `dotnetaksdemo`.


With a registry successfully set up, you can build your Docker image and push it to the registry.


Building the Docker Image

The sample application contains a Dockerfile that includes the instructions Docker will use to build and serve the API. When building the application, specify the registry DNS name:

docker build -t .

Note: Replace `` and `dotnetapi-aks-app` with the names you chose for the registry URL and login server details.


You can run the Docker image locally using the following command.

docker run -d -p 5001:80

The application will run on port 5001.


Pushing the Docker image to Azure Container Registry

Once the build process is complete, you can push the image to the container registry. To do so, you will first need to be authenticated on the container registry. Do this using the following command:

echo $AZURE_SP_PASSWORD | docker login -u $AZURE_SP --password-stdin

Note: Replace `$AZURE_SP_PASSWORD` and `$AZURE_SP` with the service principal `password` and `appId`, respectively.


Push to the container registry using the following command:

docker push

To confirm if the image has been deployed, you can run the following command:

az acr repository list --name dotnetaksdemo --output table

You will see this output:



Creating an Azure Kubernetes Service cluster

To create the AKS cluster, the Azure CLI should be connected to your Azure account.


To launch a new AKS cluster named `DotnetCluster` with two-node cluster in the resource group `AzureRG` using the Azure CLI, issue the following command:

az aks create --resource-group AzureRG --name DotnetCluster \
   --node-count 2 --enable-addons http_application_routing \
   --generate-ssh-keys --service-principal <SERVICE_PRINCIPAL_ID> \
   --client-secret <SERVICE_PRINCIPAL_PASSWORD> \
   --attach-acr dotnetaksdemo

Note: Update the `<SERVICE_PRINCIPAL_ID>` and `<SERVICE_PRINCIPAL_PASSWORD>` accordingly.


The command above includes the details of the service principal and attaches the Azure Container Registry instance created earlier to the AKS cluster.


Configuring Kubernetes manifests

The next step is to set up Kubernetes manifests for deployment. At the root of the project, create a new folder named `manifests`. This folder will contain all the Kubernetes YAML configuration.


First up is the Deployment configuration. In the `manifests` folder, create a new file named `deployment.yaml` and add the following to it:

apiVersion: apps/v1
kind: Deployment
  name: dotnetapi
  namespace: dotnetapi
    app: dotnetapi
  replicas: 3
      app: dotnetapi
        app: dotnetapi
        "": windows
        - name: dotnetapi-aks-app
          image: dotnetapi-aks-app
            - name: http
              containerPort: 80

Next, add the Namespace configuration. Create a file named ` namespace.yaml ` and add the following to it:

apiVersion: v1
kind: Namespace
  name: dotnetapi
    name: dotnetapi

Next, add the Service configuration. Create a new file named `service.yaml` and add the following to it:

apiVersion: v1
kind: Service
  name: dotnetapi
  namespace: dotnetapi
    app: dotnetapi
  type: LoadBalancer
    - port: 80
      targetPort: 80
    app: dotnetapi

Finally, add the configuration for Kustomize. Create a new file named `kustomization.yaml` and add the following to it:

kind: Kustomization
  - deployment.yaml
  - service.yaml
  - namespace.yaml
namespace: dotnetapi
  - name: dotnetapi-aks-app
    newName: <ACR_SERVER_NAME>
    newTag: v1


Configuring CircleCI

The last piece of the puzzle is the CircleCI pipeline, which automates and streamlines the entire process of deploying updates. In the root of the project, create a new folder named `.circleci` and in it add a new file named `config.yml`. Add the following to the newly created file:

version: 2.1
  azure-aks: circleci/azure-aks@0.3.0
  kubernetes: circleci/kubernetes@1.3.1
      - image: cimg/base:2023.05
      - checkout
      - setup_remote_docker:
          docker_layer_caching: true
      - run:
          name: Build and push Docker image
          command: |
            docker build -t$CIRCLE_SHA1 .
            echo $AZURE_SP_PASSWORD | docker login -u $AZURE_SP --password-stdin
            docker push$CIRCLE_SHA1
      - image: cimg/base:2023.05
        description: |
          Name of the AKS cluster
        type: string
        description: |
          Resource group that the cluster is in
        type: string
      - checkout
      - run:
          name: Pull Updated code from repo
          command: |
            git pull origin $CIRCLE_BRANCH
      - azure-aks/update-kubeconfig-with-credentials:
          cluster-name: << parameters.cluster-name >>
          install-kubectl: true
          perform-login: true
          resource-group: << parameters.resource-group >>
      - kubernetes/create-or-update-resource:
          resource-file-path: manifests/$APP_NAME.yaml
          resource-name: kustomization/$APP_NAME
      - image: cimg/base:2023.05
      - run:
          name: Install kustomize
          command: |
            curl -L $URL | tar zx
            [ -w /usr/local/bin ] && SUDO="" || SUDO=sudo
            $SUDO chmod +x ./kustomize
            $SUDO mv ./kustomize /usr/local/bin
      - checkout
      - run:
          name: Bump Docker Tag.
          command: |
            cd manifests
            kustomize edit set image $APP_NAME=$ACR_LOGIN_SERVER/$APP_NAME:$CIRCLE_SHA1
            kustomize build . > $APP_NAME.yaml
      - add_ssh_keys:
            - "$SSH_FINGERPRINT"
      - run:
          name: Commit & Push to GitHub
          command: |
            git config "$GITHUB_EMAIL"
            git config "CircleCI User"
            git checkout $CIRCLE_BRANCH           
            git add manifests/$APP_NAME.yaml
            git add manifests/kustomization.yaml
            git commit -am "Bumps docker tag [skip ci]"
            git push origin $CIRCLE_BRANCH
      - build-docker-image
      - bump-docker-tag-kustomize:
            - build-docker-image
      - aks-deploy:
          cluster-name: $CLUSTER_NAME
          resource-group: $RESOURCE_GROUP
            - bump-docker-tag-kustomize

This is the configuration file for your CircleCI pipeline. A pipeline is made up of workflows, which are collections of jobs composed of individual steps, or executable commands. This configuration file specifies three jobs: 

  • `build-docker-image` builds an updated Docker image using the checked out code from the associated GitHub repository and pushes this code to Azure Container Registry.
  • `bump-docker-tag-kustomize` updates the Docker image tag as well as the necessary Kubernetes configuration.
  • `aks-deploy` deploys the changes to the Azure Kubernetes Service (AKS) cluster.


Note the use of the `orbs` key at the top of the config file. Orbs are reusable snippets of code that help automate repeated processes, speed up project setup, and make it easy to integrate third-party tools from CircleCI’s technology partners and community members into your CI/CD pipeline. In this case, you are using two orbs to simplify your AKS and Kubernetes jobs: 

  • azure-aks
  • kubernetes


The azure-aks orb provides streamlined commands for operations like authenticating with AKS, updating `kubeconfig`, deploying resources, and more. The kubernetes orb simplifies tasks such as creating or updating Kubernetes resources, validating configuration files, managing secrets and config maps, and interacting with the Kubernetes cluster using kubectl. Learn more about orbs in the CircleCI documentation.


Also note the use of `docker_layer_caching: true`. This enables CircleCI’s Docker layer caching feature, which saves Docker image layers created within your jobs and caches them for reuse during future builds. Docker layer caching can save significant build time by ensuring only those layers that have been modified are rebuilt during your next pipeline run.


To run this project on your CircleCI account, you will need to migrate your code to a repository on your GitHub account. Create a new repository and then run the following commands. Remember to replace `REPOSITORY_URL` with the URL of the newly created repository:

git remote set-url origin <REPOSITORY_URL>
git add .
git commit -m "CI/CD Pipeline configuration"
git push origin main


Setting up the project on CircleCI

Next, connect the Github repository to your CircleCI account. Go to your CircleCI dashboard and select the Projects tab on the left panel. Click the Set Up Project button corresponding to the GitHub repository that contains the code.


The next step is to select your `config.yml` file. You can select the Fastest option because you have included the configuration in your repository. Type in the branch name (`main` in our case) and click Set Up Project.


On the first run, the process will fail because you haven’t set up a user key and added all the environment variables CircleCI needs to successfully run the pipeline.


To set up the user key, select SSH Keys from the left panel of the Project Settings page. From the User Key section, click Authorize with GitHub. CircleCI uses the user key to push changes to your GitHub account on behalf of the repository owner during workflow execution.


To configure the environment variables, select Environment Variables from the left panel of the Project Settings page. Select Add Environment Variable and type the environment variable and the value you want it assigned to.


Here are the required environment variables for this project: 

  • `APP_NAME` is the Container Image Name (`dotnetapi-aks-app`).
  • `AZURE_SP` is the username for your Azure Service Principal.
  • `AZURE_SP_PASSWORD` is the password for your Azure Service Principal.
  • `AZURE_SP_TENANT` is the tenant ID for your Azure Service Principal.
  • `CLUSTER_NAME` is the AKS cluster name (`DotnetCluster`).
  • `RESOURCE_GROUP` is the name of the AKS Resource Group (`AzureRG`).
  • `SSH_FINGERPRINT` is the SSH Fingerprint of the user key used for pushing the updated Docker tag to GitHub.


Note: To locate the `SSH_FINGERPRINT`, go to Project Settings and select SSH Keys from the sidebar. Scroll down to the User Key section, then copy the key. This key is displayed only after you click the Authorize with GitHub button.


With these variables in place, you can rerun the workflow. However, instead of starting from the beginning, you can restart from where the workflow failed.


This time, the entire process runs without any errors and your build status is set to `Success`:


Now, with any change to your application code, CircleCI will automatically build a new container image and push it to the Azure Container Registry, update the tags in your Kubernetes manifests, and deploy the new version of the application to your AKS cluster. Automating your build and deployment workflow increases team velocity by eliminating time-consuming manual setup and eliminating the risk of costly misconfiguration errors.


Accessing the application on AKS

With your application deployed successfully, you can now interact with the application hosted on the AKS cluster. To do that, you will need the external IP of your AKS cluster:

az aks get-credentials --resource-group AzureRG --name DotnetCluster
kubectl get all --namespace dotnetapi


To access your API, navigate to `http://<EXTERNAL_IP>/api/weather` in your browser.


Next steps and conclusion

Adding continuous integration and delivery to your application development flow can significantly reduce the complexity and overhead associated with managing containerized applications. In this tutorial, you learned how to set up a fast and secure CI/CD pipeline with CircleCI to automatically build and deploy a containerized Windows application to Azure Kubernetes Service. Next, you can extend the functionality of your pipeline by adding automated tests and DevSecOps tools such as application and image scans to increase the security and reliability of your containerized workloads.


Learn more about accelerating your Windows development lifecycle with continuous integration and delivery by contacting CircleCI for a customized demo today.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.