
Learn how to deploy .NET microservices to Azure Kubernetes Service (AKS) with Docker, YAML, and CI/CD. Step-by-step AKS tutorial with C# code—start now.
Azure Kubernetes Service (AKS) has become the default way modern teams run containerized workloads in the cloud, and if you build with .NET, it is one of the most powerful tools you can add to your stack. In this tutorial you will learn how to deploy .NET microservices to Azure Kubernetes Service step by step—from containerizing an ASP.NET Core Web API with Docker to writing production-ready Kubernetes YAML and rolling out updates safely. Whether you are a beginner searching "how to deploy .NET to Kubernetes" or a senior engineer looking for AKS best practices, this guide covers the practical WHY behind every step.
What Is Azure Kubernetes Service (AKS)?
Azure Kubernetes Service is Microsoft's managed Kubernetes offering. Kubernetes is an open-source container orchestrator that handles scheduling, scaling, self-healing, and networking for your containers. Running Kubernetes yourself means managing the control plane—the API server, scheduler, etcd, and controller manager—which is complex and error-prone. AKS manages the control plane for you (and it's free; you only pay for the worker nodes), so you focus on your .NET microservices instead of cluster plumbing.
For .NET developers, AKS is a natural fit. ASP.NET Core is cross-platform, produces small Linux container images, and starts fast—exactly the properties Kubernetes rewards. Combined with Azure Container Registry (ACR), Azure Monitor, and managed identity, AKS gives you an end-to-end path from source code to scalable production cloud infrastructure.
Why Microservices on Kubernetes?
Microservices split a large application into small, independently deployable services—each owning its own data and business capability. The challenge is operational: now you have ten services instead of one, each needing deployment, scaling, health checks, and service discovery. This is precisely the problem Kubernetes solves. It gives every service a stable network identity, restarts crashed containers automatically, and scales replicas up or down based on load. That is why "deploy .NET microservices to AKS" is one of the highest-demand cloud skills in the USA, UK, Canada, and Australia today.
Prerequisites for This AKS Tutorial
- An active Azure subscription (a free trial works fine)
- The Azure CLI (
az) installed and logged in - .NET 8 SDK or later
- Docker Desktop for building container images
- kubectl, the Kubernetes command-line tool
Verify your tooling quickly:
// Run these in your terminal
az --version
dotnet --version
docker --version
kubectl version --client
Step 1: Build a Sample .NET Microservice
Let's create a minimal ASP.NET Core Web API that will act as our microservice. It exposes a product endpoint and, critically, a health check endpoint that Kubernetes will use to decide whether the container is alive and ready for traffic.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddHealthChecks();
var app = builder.Build();
app.MapControllers();
// Kubernetes probes hit these endpoints
app.MapHealthChecks("/healthz/live"); // liveness
app.MapHealthChecks("/healthz/ready"); // readiness
app.MapGet("/api/products", () => new[]
{
new { Id = 1, Name = "Keyboard", Price = 49.99m },
new { Id = 2, Name = "Monitor", Price = 199.99m }
});
app.Run();
Why two health endpoints? A liveness probe answers "is the process broken and needing a restart?" A readiness probe answers "is this instance ready to receive traffic right now?" Separating them prevents Kubernetes from sending requests to a pod that is still warming up (loading config, opening a database pool), which is a common cause of intermittent 500 errors in production.
Step 2: Containerize the .NET App with Docker
AKS runs containers, so you need a Docker image. Use a multi-stage Dockerfile: one stage compiles the app with the full SDK, and a slim runtime stage ships only what's needed. This keeps the final image small and reduces your attack surface.
# ---- Build stage ----
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY *.csproj ./
RUN dotnet restore
COPY . ./
RUN dotnet publish -c Release -o /app --no-restore
# ---- Runtime stage ----
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime
WORKDIR /app
COPY --from=build /app ./
# Run as a non-root user for security
USER $APP_UID
EXPOSE 8080
ENTRYPOINT ["dotnet", "ProductService.dll"]
Best practice: copy *.csproj and run dotnet restore before copying the rest of the source. Docker caches layers, so as long as your dependencies don't change, rebuilds skip the slow restore step. Running as a non-root user ($APP_UID is built into the official .NET images) is a security best practice that AKS security policies often require.
Step 3: Push to Azure Container Registry
AKS pulls images from a registry. Azure Container Registry integrates tightly with AKS through managed identity, so you never store registry passwords in your cluster.
# Create a resource group and registry
az group create --name rg-aks-demo --location eastus
az acr create --resource-group rg-aks-demo --name myacrdemo123 --sku Basic
# Build and push the image directly in Azure (no local Docker push needed)
az acr build --registry myacrdemo123 --image productservice:v1 .
az acr build is a neat trick: it uploads your source and builds the image in the cloud, so you get consistent builds even from a low-powered laptop or a CI runner without Docker.
Step 4: Create the AKS Cluster
Now provision the Azure Kubernetes Service cluster and attach the registry so image pulls are automatic and secure.
az aks create \
--resource-group rg-aks-demo \
--name aks-dotnet-demo \
--node-count 2 \
--enable-managed-identity \
--attach-acr myacrdemo123 \
--generate-ssh-keys
# Download credentials so kubectl targets your new cluster
az aks get-credentials --resource-group rg-aks-demo --name aks-dotnet-demo
Why --attach-acr? It grants the cluster's managed identity AcrPull permission. Without it, your pods fail with ImagePullBackOff—one of the most-searched AKS errors. Managed identity means no secrets to rotate and no credentials leaking into YAML.
Step 5: Write the Kubernetes Deployment YAML
This is the heart of deploying .NET microservices to Kubernetes. A Deployment declares your desired state—how many replicas, which image, what resources—and Kubernetes continuously reconciles reality to match it.
apiVersion: apps/v1
kind: Deployment
metadata:
name: productservice
spec:
replicas: 3
selector:
matchLabels:
app: productservice
template:
metadata:
labels:
app: productservice
spec:
containers:
- name: productservice
image: myacrdemo123.azurecr.io/productservice:v1
ports:
- containerPort: 8080
resources:
requests: # guaranteed minimum
cpu: "100m"
memory: "128Mi"
limits: # hard ceiling
cpu: "500m"
memory: "256Mi"
livenessProbe:
httpGet:
path: /healthz/live
port: 8080
initialDelaySeconds: 10
periodSeconds: 15
readinessProbe:
httpGet:
path: /healthz/ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
Why set resource requests and limits? Requests tell the scheduler how much CPU and memory to reserve, so it places pods on nodes that can actually run them. Limits protect neighbors from a runaway container. Skipping these is the number-one cause of unstable AKS clusters where one memory-leaking service takes down others on the same node.
Expose the Service
Pods are ephemeral and get new IPs when they restart, so you never talk to them directly. A Service gives your microservice a stable DNS name and load-balances across replicas.
apiVersion: v1
kind: Service
metadata:
name: productservice
spec:
type: LoadBalancer # provisions an Azure public IP
selector:
app: productservice
ports:
- port: 80
targetPort: 8080
For a single public endpoint use LoadBalancer. For multiple microservices behind one IP with path-based routing (/products, /orders), use an Ingress controller like NGINX or the AKS Application Gateway Ingress Controller instead—that is the production pattern for real microservices architectures.
Step 6: Deploy and Verify
# Apply your manifests
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
# Watch the rollout
kubectl get pods -w
kubectl get service productservice # copy the EXTERNAL-IP
# Test the live endpoint
curl http:///api/products
When all three pods show Running and READY 1/1, your .NET microservice is live on Azure Kubernetes Service. If a pod is stuck, kubectl describe pod <name> and kubectl logs <name> are your two best debugging friends.
Step 7: Scale and Roll Out Updates Safely
Scaling is a one-liner, and Kubernetes handles the rest:
# Manual scaling
kubectl scale deployment productservice --replicas=5
# Autoscale based on CPU (Horizontal Pod Autoscaler)
kubectl autoscale deployment productservice --cpu-percent=70 --min=3 --max=10
To ship a new version, push productservice:v2 to ACR and update the image. Kubernetes performs a rolling update by default—spinning up new pods and draining old ones only after the new ones pass readiness checks, so users never see downtime.
kubectl set image deployment/productservice \
productservice=myacrdemo123.azurecr.io/productservice:v2
# If something breaks, roll back instantly
kubectl rollout undo deployment/productservice
AKS Best Practices for .NET Microservices
- Always define liveness and readiness probes. Without them, Kubernetes can't self-heal or route traffic correctly.
- Set resource requests and limits on every container to keep the cluster stable and cost-efficient.
- Use managed identity for ACR and Azure resources—never bake secrets into images or YAML.
- Store configuration in ConfigMaps and Secrets, not appsettings baked into the image, so the same image runs across dev, staging, and production.
- Enable Azure Monitor / Container Insights for logs, metrics, and alerting from day one.
- Pin image tags to versions (
v1,v2)—neverlatest—so deployments are reproducible. - Automate with CI/CD using GitHub Actions or Azure DevOps so every merge builds, pushes, and deploys.
Common Pitfalls to Avoid
- ImagePullBackOff: almost always a missing
--attach-acror a wrong image name. Check the registry path carefully. - CrashLoopBackOff: your app throws on startup. Check
kubectl logs—often a missing connection string or config value. - Pods pending forever: resource requests exceed node capacity. Lower requests or add nodes.
- Ignoring graceful shutdown: handle
SIGTERMso in-flight requests finish during a rollout. ASP.NET Core does this automatically, but long-running work needs aCancellationToken. - Running one giant container: resist the urge—keep each microservice single-purpose so it can scale independently.
Conclusion: Key Takeaways
Deploying .NET microservices to Azure Kubernetes Service (AKS) follows a clear, repeatable path: containerize your ASP.NET Core app with a multi-stage Dockerfile, push to Azure Container Registry, create an AKS cluster with managed identity, and describe your desired state in Kubernetes YAML. From there, Kubernetes handles scaling, self-healing, and zero-downtime rolling updates for you.
The most important lessons: always add health probes and resource limits, lean on managed identity instead of secrets, keep configuration outside your image, and automate everything with CI/CD. Master these fundamentals and you'll be able to run resilient, scalable .NET microservices on Azure Kubernetes Service with confidence. Ready to go further? Try adding an NGINX Ingress controller and a Horizontal Pod Autoscaler to your cluster next—and start deploying to AKS today.
Your go-to resource for C#, .NET, and modern software development. Follow along for daily tutorials, tips, and real-world examples.
Comments
Post a Comment