🚀 Table of Contents
- Introduction
- Required software installations and verification.
- Create the .NET Application
- Containerize Your Application
- Set Up Azure Infrastructure
- Create Kubernetes Configuration
- Set Up GitHub Actions CI/CD
- Access Your Deployed Application
- Test Continuous Deployment
- Clean Up Resources (Optional)
- Conclusion.
🚀 1. Introduction
Deploying a .NET 9 application to Azure Kubernetes Service (AKS) unlocks the full potential of cloud-native architecture with cutting-edge performance and scalability. This article outlines a streamlined deployment workflow—from containerizing your .NET 9 codebase to orchestrating it within AKS clusters. Whether you're modernizing legacy systems or launching new microservices, you'll gain practical insights to build, ship, and scale with confidence in the Azure ecosystem. 

🚀 2. Required software installations and verification
- Visual Studio Code - Download here
- NET 8 SDK - Download here
- Git - Download here
- Docker Desktop - Download here
- Important: Start Docker Desktop after installation
- Azure CLI - Download here
- kubectl - Download here
- GitHub CLI (Optional) - Download here
 🚀 3.0 CREATE THE .NET APPLICATION
3.1 Set Up Project Structure
First, open your VSCode and set your files to "autosave. " Then, navigate to the file on your laptop and create a new folder named "DotNetApp." 🚀 3.0 CREATE THE .NET APPLICATION
3.1 Set Up Project Structure
First, open your VSCode and set your files to "autosave. " Then, navigate to the file on your laptop and create a new folder named "DotNetApp."     Create the new Folder DotNetApp or choose any name of your choice Create the new Folder DotNetApp or choose any name of your choice Open the new Folder Open the new Folder Open the terminal, click on the top screen's view section, and then select terminal. Open the terminal, click on the top screen's view section, and then select terminal. Run the following code in VSCode as shown in the screenshots: mkdir weather-app-demo
cd weather-app-demo
mkdir WeatherApp
cd WeatherApp Run the following code in VSCode as shown in the screenshots: mkdir weather-app-demo
cd weather-app-demo
mkdir WeatherApp
cd WeatherApp 3.2 Initialize .NET Project
Run dotnet new webapi -minimal 3.2 Initialize .NET Project
Run dotnet new webapi -minimal 3.3. Add Required Dependencies
Run the following separately: a. dotnet add package Microsoft.Extensions.Diagnostics.HealthChecks 3.3. Add Required Dependencies
Run the following separately: a. dotnet add package Microsoft.Extensions.Diagnostics.HealthChecks b. dotnet add package Swashbuckle.AspNetCore b. dotnet add package Swashbuckle.AspNetCore 3.4. Create the Application Code
Replace the contents of Program.cs with the code below;
using Swashbuckle.AspNetCore.SwaggerUI; 3.4. Create the Application Code
Replace the contents of Program.cs with the code below;
using Swashbuckle.AspNetCore.SwaggerUI;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container
builder.Services.AddHealthChecks();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// Configure Kestrel to listen on port 8080 (required for containers)
builder.WebHost.ConfigureKestrel(options =>
{
    options.ListenAnyIP(8080);
});
var app = builder.Build();
// Configure the HTTP request pipeline
if (app.Environment.IsDevelopment() || app.Environment.IsProduction())
{
    app.UseSwagger();
    app.UseSwaggerUI(c =>
    {
        c.SwaggerEndpoint("/swagger/v1/swagger.json", "Weather API v1");
        c.RoutePrefix = "swagger";
    });
}
// Define API endpoints
app.MapGet("/", () => new
{
    Message = "Welcome to the Weather App!",
    Version = "1.0.0",
    Environment = app.Environment.EnvironmentName,
    Timestamp = DateTime.UtcNow
})
.WithName("GetWelcome")
.WithTags("General");
app.MapGet("/weather", () =>
{
    var summaries = new[]
    {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild",
        "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    };
var forecast = Enumerable.Range(1, 5).Select(index => new
{
    Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
    TemperatureC = Random.Shared.Next(-20, 55),
    Summary = summaries[Random.Shared.Next(summaries.Length)]
})
.Select(temp => new
{
    temp.Date,
    temp.TemperatureC,
    TemperatureF = 32 + (int)(temp.TemperatureC / 0.5556),
    temp.Summary
});
return forecast;
})
.WithName("GetWeatherForecast")
.WithTags("Weather");
// Health check endpoint (required for Kubernetes)
app.MapHealthChecks("/health")
.WithTags("Health");
app.Run();

3.5. Test Locally
Run dotnet run

Open your browser and test these endpoints:
http://localhost:8080/ - Welcome message

http://localhost:8080/weather - Weather forecast

http://localhost:8080/swagger - API documentation

http://localhost:8080/health - Health check

Press Ctrl+C to stop the application.

🚀 4. CONTAINERIZE YOUR APPLICATION
4.1. Create a Dockerfile file (no extension) in the Weatherapp folder. Paste the code below into the file;
Multi-stage build for optimized image size
Build stage
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
âś… Copy csproj from current dir (Dockerfile context = Weatherapp/)
COPY ["Weatherapp.csproj", "./"]
RUN dotnet restore "Weatherapp.csproj"
Copy source code and build
COPY . .
RUN dotnet build "Weatherapp.csproj" -c $BUILD_CONFIGURATION -o /app/build
Publish stage
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "Weatherapp.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
Runtime stage
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
WORKDIR /app
EXPOSE 8080
ENV ASPNETCORE_URLS=http://+:8080
ENV ASPNETCORE_ENVIRONMENT=Production
Install curl
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
Final stage
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "WeatherApp.dll"]

4.2 Create .dockerignore
Create a .dockerignore file to exclude unnecessary files. Paste the file below into the Dockerignore file.
Build outputs
bin/
obj/
out/
IDE files
.vs/
.vscode/
*.user
*.suo
OS files
.DS_Store
Thumbs.db
Git
.git/
.gitignore
Documentation
README.md
*.md
Docker files
Dockerfile
Dockerfile.*
.dockerignore
Logs
.log
logs/

**4.3 Build and Test Container*
Run the following code;  
Build the Docker image
docker build -t weather-app: local . 

Run the container
docker run -d -p 8080:8080 --name weather-test weather-app:local

Test the containerized app
curl http://localhost:8080/

curl http://localhost:8080/weather

# Clean up
Run docker stop weather-test

docker rm weather-test

5. SET UP AZURE INFRASTRUCTURE
Run az login 
This connects to your Azure account and opens up a list of subscriptions.

5.2 Select your subscription
Choose your subscription from the list of others, or simply run the following; 
List available subscriptions
az account list --output table
Set the subscription you want to use
az account set --subscription "Your-Subscription-Name"
5.3. Create a resource group
Run the following code;
az group create --name student-demo --location eastus

5.4. Create a Container Registry
Replace 'studentdemo2024acr' with a unique name (add your initials/year)
az acr create \
  --resource-group student-demo \
  --name studentdemo2074acr \
  --sku Basic

5.5. Build and Push Image Using ACR
From the folder that contains your Dockerfile, run:
az acr build \
  --registry studentdemo2074acr \
  --image weather-app:latest \
  .


5.6 Create AKS Cluster with ACR integration.
Run this code 
az aks create \
  --resource-group student-demo \
  --name student-aks-cluster \
  --node-count 1 \
  --node-vm-size Standard_B2s \
  --attach-acr studentdemo2074acr \
  --enable-managed-identity \
  --generate-ssh-keys

5.7. Connect To Your Cluster
Download cluster credentials
az aks get-credentials \
  --resource-group student-demo \
  --name student-aks-cluster

# Verify connection
kubectl get nodes

5.8. Verify ACR Integration (Optional)
Get the managed identity client ID
Run the code; 
CLIENT_ID=$(az aks show \
  --resource-group student-demo \
  --name student-aks-cluster \
  --query "identityProfile.kubeletidentity.clientId" \
  --output tsv)
echo "Kubelet Client ID: $CLIENT_ID" 

# Verify the role assignment exists
az role assignment list \
  --assignee $CLIENT_ID \
  --scope $(az acr show --name studentdemo2074acr --query id --output tsv) \
  --output table

You should see an AcrPull role assignment. If not, create it manually:
Only if the role assignment doesn't exist
Run;
az role assignment create \
  --assignee $CLIENT_ID \
  --role AcrPull \
  --scope $(az acr show --name studentdemo2074acr --query id --output tsv)
6. CREATE KUBERNETES CONFIGURATION
6.1. Set Up Kubernetes Manifests Directory
Run the following code; 
cd ..  (# Back to weather-app-demo folder) 

mkdir k8s
cd k8s

6.2. Create Deployment Configuration
Create deployment.yaml 
paste the code below into the file;
apiVersion: apps/v1
kind: Deployment
metadata:
  name: weather-app
  namespace: default
  labels:
    app: weather-app
    version: v1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: weather-app
  template:
    metadata:
      labels:
        app: weather-app
        version: v1
    spec:
      imagePullSecrets:
        - name: acr-pull-secret
      containers:
        - name: weather-app
          image: studentdemo2074acr.azurecr.io/weather-app:
      imagePullPolicy: Always
      ports:
        - name: http
          containerPort: 8080
          protocol: TCP
      env:
        - name: ASPNETCORE_ENVIRONMENT
          value: "Production"
      resources:
        requests:
          memory: "128Mi"
          cpu: "100m"
        limits:
          memory: "512Mi"
          cpu: "500m"
      livenessProbe:
        httpGet:
          path: /health
          port: http
        initialDelaySeconds: 30
        periodSeconds: 10
        timeoutSeconds: 5
        failureThreshold: 3
      readinessProbe:
        httpGet:
          path: /health
          port: http
        initialDelaySeconds: 10
        periodSeconds: 5
        timeoutSeconds: 3
        failureThreshold: 3
      startupProbe:
        httpGet:
          path: /health
          port: http
        initialDelaySeconds: 15
        periodSeconds: 5
        timeoutSeconds: 3
        failureThreshold: 30

Note: There's no need for imagePullSecrets because AKS with --attach-acr handles authentication automatically!
6.3. Create Service Configuration
Create service.yaml and paste the code below into the file.
apiVersion: v1
kind: Service
metadata:
  name: weather-app-service
  labels:
    app: weather-app
spec:
  type: LoadBalancer
  selector:
    app: weather-app
  ports:
- name: http
port: 80
targetPort: 8080
protocol: TCP
 6.4 Deploy to Kubernetes 
Run the codes individually. 
# Apply Kubernetes manifests
kubectl apply -f deployment.yaml 6.4 Deploy to Kubernetes 
Run the codes individually. 
# Apply Kubernetes manifests
kubectl apply -f deployment.yaml kubectl apply -f service.yml kubectl apply -f service.yml # Check deployment status
kubectl get deployments # Check deployment status
kubectl get deployments kubectl get pods kubectl get pods kubectl get services kubectl get services  
Watch pods until they're running
kubectl get pods --watch

6.5. Troubleshoot if Needed
If you see ImagePullBackOff:
Check pod events
kubectl describe pod $(kubectl get pods -l app=weather-app -o jsonpath='{.items[0].metadata.name}')
Check if the image exists in ACR
az acr repository show-tags --name studentdemo2024acr --repository weather-app --output table
Force a new pull
kubectl rollout restart deployment/weather-app
7.0. SET UP GITHUB ACTIONS CI/CD
7.1. Initialize Git Repository
Run the following codes
cd ..  (# Back to weather-app-demo folder)

Initialize Git repository
git init
git add .
git commit -m "Initial commit: Weather App with Docker and Kubernetes"

7.2. Create Azure Service Principal
Get your subscription ID
SUBSCRIPTION_ID=$(az account show --query id --output tsv)
echo "Subscription ID: $SUBSCRIPTION_ID"

Create service principal with contributor role
az ad sp create-for-rbac \
  --name "weather-app-github-sp" \
  --role contributor \
  --scopes /subscriptions/$SUBSCRIPTION_ID/resourceGroups/student-demo \
  --sdk-auth

Important: Copy the entire JSON output in the red box - you'll need it for GitHub secrets!
7.3. Create GitHub Repository
Create GitHub repository (using GitHub CLI)
gh auth login  # Follow the prompts



gh repo create weather-app-demo --public --source=. --push

Alternative: Create the repository manually on GitHub.com and push your code.
7.4. Configure GitHub Secrets
Navigate to your GitHub repository:
Go to Settings → Secrets and variables → Actions
Click New repository secret and add these secrets:

Navigate to secrets and variables 



Secret Name
Value
AZURE_CREDENTIALS
The JSON output from step 5.2
ACR_NAME
studentdemo2074acr (your ACR name)
RESOURCE_GROUP
student-demo
CLUSTER_NAME
student-aks-cluster




7.5. Create GitHub Workflow
Create the workflow directory and file by running the code below;
mkdir -p .github/workflows
touch .github/workflows/deploy.yml
name: Build and Deploy to AKS
on:
  push:
    branches: [main]
  workflow_run:
    workflows: ["CI - Build and Test"]
    types: [completed]
    branches: [main]
jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'push' }}
steps:
  - name: Checkout code
    uses: actions/checkout@v4
  - name: Setup .NET 9.0
    uses: actions/setup-dotnet@v4
    with:
      dotnet-version: '9.0.x'
  - name: Restore dependencies
    run: dotnet restore WeatherApp/WeatherApp.csproj
  - name: Build application
    run: dotnet build WeatherApp/WeatherApp.csproj --configuration Release --no-restore
  - name: Run tests
    run: dotnet test WeatherApp/WeatherApp.csproj --no-build --verbosity normal
  - name: Azure Login
    uses: azure/login@v1
    with:
      creds: ${{ secrets.AZURE_CREDENTIALS }}
  - name: Login to Azure Container Registry
    run: |
      az acr login --name ${{ secrets.ACR_NAME }}
  - name: Set IMAGE_NAME environment variable
    run: echo "IMAGE_NAME=weather-app" >> $GITHUB_ENV
  - name: Set NAMESPACE environment variable
    run: echo "NAMESPACE=default" >> $GITHUB_ENV
  - name: Build and push Docker image to ACR
    run: |
      IMAGE_TAG=${{ secrets.ACR_NAME }}.azurecr.io/$IMAGE_NAME:${{ github.sha }}
      docker build -t $IMAGE_TAG WeatherApp/
      docker push $IMAGE_TAG
      echo "IMAGE_TAG=$IMAGE_TAG" >> $GITHUB_ENV
  - name: Test Docker image
    run: |
      docker run -d -p 8080:8080 -e ASPNETCORE_URLS=http://+:8080 --name test-container $IMAGE_TAG
      sleep 10
      docker logs test-container
      curl -f http://localhost:8080/health || exit 1
      docker stop test-container
      docker rm test-container
  - name: Deploy to AKS
    if: github.ref == 'refs/heads/main'
    run: |
      # Get AKS credentials
      az aks get-credentials \
        --resource-group ${{ secrets.RESOURCE_GROUP }} \
        --name ${{ secrets.CLUSTER_NAME }} \
        --overwrite-existing
      # Check if deployment exists, if not create it
      if ! kubectl get deployment/weather-app -n default > /dev/null 2>&1; then
        echo "Creating new deployment..."
        if [ -d "k8s/" ]; then
          kubectl apply -f k8s/ -n default
        else
          echo "No k8s/ directory found. Please create Kubernetes manifests."
          exit 1
        fi
      else
        echo "Updating existing deployment..."
        kubectl set image deployment/weather-app \
          weather-app=$IMAGE_TAG \
          -n default \
          --record
      fi
      # Wait for deployment to complete
      kubectl rollout status deployment/weather-app -n default --timeout=300s
      # Get service info
      kubectl get service weather-app-service -n default || echo "Service not found"
      # Verify pods
      kubectl get pods -n default -l app=weather-app
      # Simple smoke test
      kubectl port-forward service/weather-app-service 8080:80 -n default &
      sleep 5
      curl -f http://localhost:8080/health || echo "Health check failed"
      pkill -f "kubectl port-forward"

7.6. 5.6 Deploy Your Application
git add .
git commit -m "Add GitHub Actions CI/CD pipeline"
git push origin main

Monitor the deployment:
Go to your GitHub repository
Click the Actions tab
Watch your workflow run in real-time

**8.0 Access Your Deployed Application
8.1 Access Your Deployed Application
Get External IP Address
Check service status
Run kubectl get service weather-app-service

Wait for EXTERNAL-IP (may take 2-5 minutes)
Run kubectl get service weather-app-service --watch

8.2. Test Your Live Application
Test the endpoints
curl http://YOUR-EXTERNAL-IP/
http://4.157.170.214

curl http://YOUR-EXTERNAL-IP/weather
http://4.157.170.214/weather

curl http://YOUR-EXTERNAL-IP/health
http://4.157.170.214/health

Or open in browser
open http://YOUR-EXTERNAL-IP/swagger  # macOS
http://4.157.170.214/swagger

start http://YOUR-EXTERNAL-IP/swagger  # Windows
9. Test Continuous Deployment
Make a Code Change
Edit WeatherApp/Program.cs and update the welcome message:
app.MapGet("/", () => new
{
    Message = "Hurray! Welcome to the Updated Weather App! 🌤️",
    Version = "1.1.0",
    Environment = app.Environment.EnvironmentName,
    Timestamp = DateTime.UtcNow,
    DeployedBy = "GitHub Actions"
})

Deploy the Changes by running the following code
git add ../Weatherapp/Program.cs
git commit -m "Update welcome message and version"




# Test the endpoints on a browser
http://YOUR-EXTERNAL-IP/swagger
http://4.157.170.214

10. Clean Up Resources (Optional)
Option 1: Delete Individual Resources
Delete Kubernetes resources
kubectl delete -f k8s/
Delete AKS cluster
az aks delete --resource-group student-demo --name student-aks-cluster --yes
Delete Container Registry
az acr delete --name studentdemo2024acr --yes
Option 2: Delete Everything (Recommended)
Delete the entire resource group (removes all Azure resources)
az group delete --name student-demo --yes --no-wait
🚀 Conclusion: Seamless Deployment, Scalable Success
Deploying a .NET 9 application to Azure Kubernetes Service (AKS) is no longer a daunting task—it’s a streamlined process that empowers developers to harness the full potential of cloud-native architecture. You ensure a robust, scalable, and resilient deployment pipeline by following the outlined procedures—from containerizing the app to configuring YAML manifests and leveraging Azure CLI. With AKS and .NET 9 working in tandem, your applications are primed for performance, agility, and future growth.
 
 
              
 
    
Top comments (0)