🚀 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."
Create the new Folder DotNetApp or choose any name of your choice
Open the new Folder
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
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
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;
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
kubectl apply -f service.yml
# Check deployment status kubectl get deployments
kubectl get pods
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)