Step-by-Step Guide to Deploy ASP.NET Core Microservices on Azure Kubernetes Service (AKS) Using Bicep and Azure Container Registry

December 1, 2025 · Asad Ali



Step-by-Step Guide to Deploy ASP.NET Core Microservices on AKS


Step-by-Step Guide to Deploy ASP.NET Core Microservices on Azure Kubernetes Service (AKS) Using Bicep and Azure Container Registry

In today’s competitive cloud landscape, the demand for scalable, containerized, and cloud-native architectures is higher than ever. This guide walks you through deploying ASP.NET Core microservices on Azure Kubernetes Service (AKS) using the modern Infrastructure as Code tool Bicep along with Docker and Azure Container Registry (ACR). Whether you’re a seasoned C# developer, a DevOps engineer, or an architect, this comprehensive tutorial provides practical, real-world examples to help you build a production-grade Kubernetes environment.

Introduction: Why Deploy ASP.NET Core Microservices on AKS

ASP.NET Core is not only one of the most popular frameworks (with a reported 26.7% usage amongst developers according to Stack Overflow Developer Survey 2020) but also a great choice for building microservices. Deploying these microservices on AKS leverages the power of container orchestration, automatic scaling, and high availability. AKS simplifies many aspects of cluster management while offering integration with the broader suite of Azure cloud services.

Key benefits include:

  • Seamless integration with Azure cloud services
  • Dynamic scaling and high availability
  • Reduced operational overhead with managed Kubernetes
  • Flexible CI/CD workflows for continuous deployment

Below is an ASCII architecture diagram that illustrates a common deployment scenario:


          +------------------+       +-----------------+
          |   ASP.NET Core   |       |      API        |
          |   Microservice   |------>|  Gateway/Ingress|
          +------------------+       +-----------------+
                   |                           |
                   v                           v
          +------------------+       +-----------------+
          |   Azure Container|       |   Load Balancer |
          |   Registry (ACR)|        | (Public Endpoint)|
          +------------------+       +------------------+
                   |                           |
                   v                           v
          +-------------------------------+
          |  Azure Kubernetes Service     |
          | (Managed Cluster, Auto-Scale) |
          +-------------------------------+
        

Prerequisites: Tools, Azure Setup, and Project Structure

Before embarking on this deployment journey, ensure that you have the following tools and setups in place:

  • Visual Studio 2019/2022 or VS Code with .NET 6/7 SDK installed
  • Docker Desktop for containerizing microservices
  • Azure CLI installed to interact with Azure resources
  • Bicep CLI for deploying Infrastructure as Code templates
  • kubectl command line tool
  • Helm package manager for Kubernetes
  • An active Azure subscription with appropriate permissions

Here is an example folder structure to organize your microservices project effectively:


        /MultiTenantApi
          /Microservices
            /ServiceA
              /Controllers
              /Models
              /Services
              ServiceA.csproj
            /ServiceB
              /Controllers
              /Models
              /Services
              ServiceB.csproj
          /Docker
            Dockerfile.serviceA
            Dockerfile.serviceB
          /Infrastructure
            aks.bicep
            acr.bicep
          /CI-CD
            azure-pipelines.yml
        

This structure separates concerns by keeping microservice implementations, Docker files, infrastructure code, and CI/CD configurations in their respective folders, aiding maintainability and scalability (Microsoft, 2020).

Step 1: Containerizing ASP.NET Core Microservices with Docker

Containerization encapsulates your application environment, ensuring consistency across development, testing, and production. Let’s start by creating a Dockerfile for our ASP.NET Core microservice. Below is an initial, “fat” Dockerfile that might be seen in legacy systems:


// BEFORE refactoring: Fat Dockerfile
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY . .
RUN dotnet restore
RUN dotnet publish -c Release -o /app

FROM mcr.microsoft.com/dotnet/aspnet:6.0
WORKDIR /app
COPY --from=build /app .
ENTRYPOINT ["dotnet", "ServiceA.dll"]
        

Refactor the Dockerfile by leveraging multi-stage builds more effectively, reducing image size and attack surface:


// AFTER refactoring: Optimized Dockerfile using multi-stage build
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /app
COPY *.csproj ./
RUN dotnet restore

COPY . .
RUN dotnet publish -c Release -o out

FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS runtime
WORKDIR /app
COPY --from=build /app/out ./
ENTRYPOINT ["dotnet", "ServiceA.dll"]
        

This optimized version separates the dependency restoration from source code copying and publishing, leading to faster rebuilds when source code changes.

Step 2: Pushing Docker Images to Azure Container Registry (ACR)

After building and testing your container locally, it’s time to push your images to ACR. Follow these steps to create an ACR and push your image:


# Log in to Azure
az login

# Create a resource group
az group create --name MyResourceGroup --location eastus

# Create an Azure Container Registry
az acr create --resource-group MyResourceGroup --name MyACRRegistry --sku Basic

# Log in to ACR
az acr login --name MyACRRegistry

# Build your Docker image and tag it with the ACR login server
docker build -t myacrregistry.azurecr.io/servicea:latest -f Docker/Dockerfile.serviceA .

# Push the image to ACR
docker push myacrregistry.azurecr.io/servicea:latest
        

This process not only creates a dedicated container registry in Azure but also securely stores your images ready for deployment in AKS. (Cloud Native Computing Foundation, 2021)

Step 3: Writing Bicep Templates to Provision Azure Kubernetes Service (AKS)

Bicep simplifies the Azure Resource Manager (ARM) templates with a more declarative syntax. Below is a sample Bicep template to provision an AKS cluster:


// aks.bicep - Provision an AKS cluster

@description('Cluster name')
param clusterName string = 'MyAKSCluster'

@description('Location for the AKS cluster')
param location string = resourceGroup().location

@description('Kubernetes version')
param kubernetesVersion string = '1.21.2'

resource aksCluster 'Microsoft.ContainerService/managedClusters@2021-03-01' = {
  name: clusterName
  location: location
  sku: {
    name: 'Basic'
  }
  properties: {
    kubernetesVersion: kubernetesVersion
    dnsPrefix: clusterName
    agentPoolProfiles: [
      {
        name: 'agentpool'
        count: 3
        vmSize: 'Standard_DS2_v2'
        osType: 'Linux'
        mode: 'System'
      }
    ]
    linuxProfile: {
      adminUsername: 'azureuser'
      ssh: {
        publicKeys: [
          {
            keyData: 'ssh-rsa AAAAB3NzaC...'
          }
        ]
      }
    }
    enableRBAC: true
    networkProfile: {
      networkPlugin: 'azure'
      loadBalancerSku: 'standard'
    }
  }
}

output aksClusterName string = aksCluster.name
        

This Bicep template clearly defines parameters for the AKS cluster, including Kubernetes version, location, VM size, and agent pool configuration. (GitHub Octoverse, 2020)

Step 4: Deploying Microservices to AKS Using kubectl and Helm

Once your cluster is provisioned, it’s crucial to deploy your microservices efficiently. You can use either native kubectl commands or Helm charts to manage deployments. Below is a sample Kubernetes deployment manifest and a Helm chart snippet:

Using kubectl:


apiVersion: apps/v1
kind: Deployment
metadata:
  name: servicea-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: servicea
  template:
    metadata:
      labels:
        app: servicea
    spec:
      containers:
        - name: servicea
          image: myacrregistry.azurecr.io/servicea:latest
          ports:
            - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: servicea-service
spec:
  type: LoadBalancer
  selector:
    app: servicea
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
        

Using Helm:


# Chart.yaml
apiVersion: v2
name: servicea
description: A Helm chart for deploying ServiceA
version: 0.1.0

# values.yaml
replicaCount: 3
image:
  repository: myacrregistry.azurecr.io/servicea
  tag: latest
  pullPolicy: Always

service:
  type: LoadBalancer
  port: 80

# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "servicea.fullname" . }}
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      app: {{ include "servicea.name" . }}
  template:
    metadata:
      labels:
        app: {{ include "servicea.name" . }}
    spec:
      containers:
        - name: servicea
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          ports:
            - containerPort: {{ .Values.service.port }}
        

These examples clearly demarcate the deployment strategy based on whether you prefer low-level kubectl control or the abstraction provided by Helm. (Microsoft, 2020)

Step 5: Configuring AKS Networking and Load Balancer for Microservices Access

To expose your microservices externally, you need to configure networking resources in AKS, including an Ingress Controller or load balancer. Below is a sample Kubernetes YAML configuration for a LoadBalancer service:


apiVersion: v1
kind: Service
metadata:
  name: servicea-loadbalancer
spec:
  type: LoadBalancer
  selector:
    app: servicea
  ports:
    - port: 80
      targetPort: 80
      protocol: TCP
        

This configuration instructs AKS to provision a cloud load balancer and maps port 80 from the clients to the container port 80. Make sure you understand the cloud networking implications, including security rules and cost considerations related to load balancing on Azure (Azure, 2023).

Bonus: Automating the CI/CD Pipeline with GitHub Actions or Azure DevOps

Automating your build and deployment pipelines improves developer productivity and ensures consistent deployments. Below is an example GitHub Actions workflow YAML that builds your Docker image, pushes it to ACR, and deploys to AKS.


name: Build and Deploy to AKS

on:
  push:
    branches:
      - main

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v2

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v1

      - name: Log in to Azure Container Registry
        uses: azure/docker-login@v1
        with:
          login-server: myacrregistry.azurecr.io
          username: ${{ secrets.ACR_USERNAME }}
          password: ${{ secrets.ACR_PASSWORD }}

      - name: Build and push Docker image
        run: |
          docker build -t myacrregistry.azurecr.io/servicea:latest -f Docker/Dockerfile.serviceA .
          docker push myacrregistry.azurecr.io/servicea:latest

      - name: Set up kubectl
        uses: azure/setup-kubectl@v1
        with:
          version: 'v1.21.2'

      - name: Deploy to AKS
        run: |
          kubectl apply -f k8s/deployment.yaml
          kubectl apply -f k8s/service.yaml
        env:
          KUBECONFIG: ${{ secrets.KUBECONFIG }}
        

This CI/CD pipeline example demonstrates transforming manual deployment processes into automatic, repeatable workflows. For Azure DevOps, consider using integrated pipelines with YAML build definitions (Microsoft, 2020).

Monitoring and Scaling Microservices on AKS

Monitoring plays a critical role in maintaining the stability and performance of your microservices. Use Azure Monitor, Prometheus, or Grafana to collect performance metrics, logs, and diagnostic data. Additionally, implementing a Horizontal Pod Autoscaler (HPA) can help your services scale automatically based on CPU or memory demands.


apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
  name: servicea-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: servicea-deployment
  minReplicas: 2
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 50
        

Integrate logging tools like Azure Log Analytics to further analyze performance and troubleshoot potential issues. Additionally, consider leveraging Kubernetes’ native metrics server for real-time insight.

Common Mistakes When Using AKS Deployment

  • Not properly configuring RBAC and network policies, which can lead to security vulnerabilities.
  • Deploying monolithic images instead of microservice-specific containers, resulting in harder scaling and maintenance.
  • Overlooking the benefits of multi-stage builds in Docker, leading to unnecessarily large images.
  • Ignoring branch or version tagging when pushing images to ACR, which can cause confusion in production deployments.
  • Using inadequate resource limits/requests in Kubernetes manifests resulting in poor autoscaling behavior.

Who Should Use AKS Deployment (and Who Should Avoid It)

This approach is excellent for teams and organizations that:

  • Are embracing microservices and containerization for their application architectures.
  • Have a development team familiar with Docker, Kubernetes, and Infrastructure as Code.
  • Require scalability, high availability, and cloud-native solutions leveraging Azure services.

However, if your team is not yet comfortable with container orchestration or has a monolithic application that does not require scaling, a traditional VM or Platform as a Service (PaaS) approach such as Azure App Service might be more suitable until you transition to a microservice architecture.

Conclusion: Best Practices and Cost Optimization Tips

This guide outlines how to containerize ASP.NET Core microservices, push them to Azure Container Registry, provision AKS using Bicep, and deploy with robust CI/CD pipelines. Always remember:

  • Follow best practices by using multi-stage Docker builds to minimize image size.
  • Maintain a clean project structure with separate folders for microservices, infrastructure, and CI/CD pipelines.
  • Use Infrastructure as Code (Bicep) for reproducible and automated environment provisioning.
  • Continuously monitor services using Azure Monitor and implement HPA for dynamic scaling.
  • Regularly review cost optimization strategies, such as scaling down resources during off-peak hours and leveraging reserved capacity offerings on Azure.

By embracing these techniques, you not only improve deployment consistency but also prepare your applications for growth and high-demand scenarios. As the Cloud Native Computing Foundation and industry surveys indicate, adopting Kubernetes and DevOps practices on Azure can significantly enhance application resilience and development velocity (CNCF, 2021; JetBrains Developer Survey, 2020).