.NET Aspire applications can be deployed to any Kubernetes cluster, including Azure Kubernetes Service (AKS), Amazon EKS, Google GKE, and on-premises clusters.
Prerequisites
Kubernetes cluster (1.19+)
kubectl installed and configured
Helm 3.x installed
Container registry accessible from your cluster
.NET Aspire application with AppHost project
Aspire CLI installed (dotnet tool install -g aspire)
Kubernetes Deployment Model
Aspire generates Kubernetes manifests from your application model, creating:
Deployments - For container workloads
Services - For service discovery and load balancing
ConfigMaps - For configuration data
Secrets - For sensitive configuration
Ingress - For external HTTP/HTTPS access (optional)
Setting Up Kubernetes Environment
Add a Kubernetes environment to your AppHost:
var builder = DistributedApplication . CreateBuilder ( args );
// Add Kubernetes environment
var k8s = builder . AddKubernetesEnvironment ( "k8s" );
// Add your services
var cache = builder . AddRedis ( "cache" );
var apiService = builder . AddProject < Projects . ApiService >( "apiservice" )
. WithReference ( cache );
builder . Build (). Run ();
The Kubernetes environment resource is implemented in src/Aspire.Hosting.Kubernetes/KubernetesEnvironmentResource.cs and automatically generates Helm charts from your app model.
Generating Kubernetes Manifests
Use the Aspire CLI to generate deployment artifacts:
# Generate Kubernetes manifests
aspire publish --output-path ./k8s-artifacts
This creates:
k8s-artifacts/
├── aspire-manifest.json # Aspire manifest
├── Chart.yaml # Helm chart metadata
├── values.yaml # Default values
└── templates/
├── apiservice-deployment.yaml # Deployment for API service
├── apiservice-service.yaml # Service for API
├── cache-deployment.yaml # Redis deployment
└── cache-service.yaml # Redis service
Helm Chart Generation
The Kubernetes environment automatically generates a Helm chart with the application name:
var k8s = builder . AddKubernetesEnvironment ( "k8s" );
// The Helm chart name is derived from the application name
// Located in: src/Aspire.Hosting.Kubernetes/KubernetesEnvironmentExtensions.cs:40
resource . HelmChartName = builder . Environment . ApplicationName . ToHelmChartName ();
Deploying to Kubernetes
Using Helm
# Install the Helm chart
helm install myapp ./k8s-artifacts
# Upgrade an existing deployment
helm upgrade myapp ./k8s-artifacts
# Uninstall
helm uninstall myapp
Using kubectl
# Apply all manifests
kubectl apply -f ./k8s-artifacts/templates/
# Check deployment status
kubectl get deployments
kubectl get pods
kubectl get services
# Delete resources
kubectl delete -f ./k8s-artifacts/templates/
Customizing Kubernetes Resources
Configuring Deployments
Customize Kubernetes deployments for your resources:
builder . AddProject < Projects . ApiService >( "apiservice" )
. WithReplicas ( 3 ) // Set replica count
. WithEnvironment ( "ASPNETCORE_ENVIRONMENT" , "Production" );
Service Configuration
Configure Kubernetes services:
builder . AddProject < Projects . ApiService >( "apiservice" )
. WithHttpEndpoint ( port : 8080 , name : "http" )
. WithHttpsEndpoint ( port : 8443 , name : "https" );
This generates a Kubernetes Service:
apiVersion : v1
kind : Service
metadata :
name : apiservice
spec :
selector :
app : apiservice
ports :
- name : http
port : 8080
targetPort : 8080
protocol : TCP
- name : https
port : 8443
targetPort : 8443
protocol : TCP
type : ClusterIP
External Endpoints
Expose services externally:
builder . AddProject < Projects . WebApp >( "webapp" )
. WithExternalHttpEndpoints ();
This creates an Ingress resource for external access.
Container Images
Building and Pushing Images
Before deploying, build and push container images:
# For .NET projects, Aspire generates Dockerfiles
# Build and push manually or use CI/CD
docker build -t myregistry.azurecr.io/apiservice:latest ./ApiService
docker push myregistry.azurecr.io/apiservice:latest
Using Private Registries
Configure Kubernetes to use a private registry:
# Create a docker-registry secret
kubectl create secret docker-registry regcred \
--docker-server=myregistry.azurecr.io \
--docker-username= < username > \
--docker-password= < password >
Reference the secret in your deployment:
spec :
imagePullSecrets :
- name : regcred
containers :
- name : apiservice
image : myregistry.azurecr.io/apiservice:latest
Configuration Management
ConfigMaps
Aspire generates ConfigMaps for non-sensitive configuration:
builder . AddProject < Projects . ApiService >( "apiservice" )
. WithEnvironment ( "LOG_LEVEL" , "Information" )
. WithEnvironment ( "FEATURE_FLAGS" , "EnableNewUI=true" );
Generated ConfigMap:
apiVersion : v1
kind : ConfigMap
metadata :
name : apiservice-config
data :
LOG_LEVEL : "Information"
FEATURE_FLAGS : "EnableNewUI=true"
Secrets
Use Kubernetes Secrets for sensitive data:
var password = builder . AddParameter ( "db-password" , secret : true );
var db = builder . AddPostgres ( "postgres" )
. WithEnvironment ( "POSTGRES_PASSWORD" , password );
builder . AddProject < Projects . ApiService >( "apiservice" )
. WithReference ( db );
Generated Secret:
apiVersion : v1
kind : Secret
metadata :
name : postgres-secret
type : Opaque
data :
POSTGRES_PASSWORD : <base64-encoded-password>
Resource Scaling
Horizontal Pod Autoscaling
Configure HPA for automatic scaling:
apiVersion : autoscaling/v2
kind : HorizontalPodAutoscaler
metadata :
name : apiservice-hpa
spec :
scaleTargetRef :
apiVersion : apps/v1
kind : Deployment
name : apiservice
minReplicas : 2
maxReplicas : 10
metrics :
- type : Resource
resource :
name : cpu
target :
type : Utilization
averageUtilization : 70
Resource Limits
Set CPU and memory limits:
builder . AddProject < Projects . ApiService >( "apiservice" )
. WithAnnotation ( new ResourceLimitsAnnotation
{
CpuLimit = "1000m" ,
MemoryLimit = "512Mi" ,
CpuRequest = "100m" ,
MemoryRequest = "128Mi"
});
Service Discovery
Kubernetes DNS provides automatic service discovery:
// Reference another service
var cache = builder . AddRedis ( "cache" );
builder . AddProject < Projects . ApiService >( "apiservice" )
. WithReference ( cache ); // Resolves to: cache:6379
In the container, the connection string resolves to the Kubernetes service DNS name.
Persistent Storage
Volumes for Databases
var postgres = builder . AddPostgres ( "postgres" )
. WithDataVolume (); // Creates a PersistentVolumeClaim
builder . AddProject < Projects . DataService >( "dataservice" )
. WithReference ( postgres );
Generated PersistentVolumeClaim:
apiVersion : v1
kind : PersistentVolumeClaim
metadata :
name : postgres-data
spec :
accessModes :
- ReadWriteOnce
resources :
requests :
storage : 1Gi
Bind Mounts
builder . AddContainer ( "nginx" , "nginx" )
. WithBindMount ( "./static" , "/usr/share/nginx/html" , isReadOnly : true );
Health Checks
Aspire configures Kubernetes health probes:
apiVersion : apps/v1
kind : Deployment
metadata :
name : apiservice
spec :
template :
spec :
containers :
- name : apiservice
livenessProbe :
httpGet :
path : /health
port : 8080
initialDelaySeconds : 30
periodSeconds : 10
readinessProbe :
httpGet :
path : /ready
port : 8080
initialDelaySeconds : 5
periodSeconds : 5
Deploying to Cloud Kubernetes Services
Azure Kubernetes Service (AKS)
# Create AKS cluster
az aks create --resource-group myResourceGroup \
--name myAKSCluster \
--node-count 3 \
--enable-addons monitoring
# Get credentials
az aks get-credentials --resource-group myResourceGroup --name myAKSCluster
# Deploy
helm install myapp ./k8s-artifacts
Amazon EKS
# Create EKS cluster
eksctl create cluster --name myCluster --region us-west-2
# Update kubeconfig
aws eks update-kubeconfig --region us-west-2 --name myCluster
# Deploy
helm install myapp ./k8s-artifacts
Google GKE
# Create GKE cluster
gcloud container clusters create myCluster --num-nodes=3
# Get credentials
gcloud container clusters get-credentials myCluster
# Deploy
helm install myapp ./k8s-artifacts
Observability
Logging
Aspire applications emit logs to stdout/stderr, which Kubernetes captures:
# View logs
kubectl logs deployment/apiservice
# Follow logs
kubectl logs -f deployment/apiservice
# Logs from all pods
kubectl logs -l app=apiservice
Metrics
Integrate with Prometheus for metrics:
apiVersion : v1
kind : Service
metadata :
name : apiservice
annotations :
prometheus.io/scrape : "true"
prometheus.io/port : "8080"
prometheus.io/path : "/metrics"
Distributed Tracing
Configure OpenTelemetry export to a trace collector:
builder . AddProject < Projects . ApiService >( "apiservice" )
. WithEnvironment ( "OTEL_EXPORTER_OTLP_ENDPOINT" , "http://jaeger:4317" );
Best Practices
Organize resources by environment: kubectl create namespace production
helm install myapp ./k8s-artifacts --namespace production
Configure Resource Limits
Use Liveness and Readiness Probes
Ensure Kubernetes can manage your application lifecycle: builder . AddProject < Projects . ApiService >( "apiservice" )
. WithHealthCheck ( "/health" );
Use external secret managers like Azure Key Vault or AWS Secrets Manager: # Use External Secrets Operator
kubectl apply -f external-secret.yaml
Always tag images with versions, not latest: docker build -t myregistry.azurecr.io/apiservice:v1.2.3 .
Troubleshooting
Pod Not Starting
Image Pull Errors
Service Not Accessible
Container Crashes
Check pod status and events: kubectl describe pod < pod-nam e >
kubectl get events --sort-by= '.lastTimestamp'
Verify image exists and credentials are correct: kubectl describe pod < pod-nam e > | grep -A 5 "Failed"
# Check secret
kubectl get secret regcred -o yaml
Check service and endpoints: kubectl get svc
kubectl get endpoints
kubectl port-forward service/apiservice 8080:8080
View logs for crash details: kubectl logs < pod-nam e > --previous
Next Steps
Deploy to Azure Learn about deploying to Azure Container Apps
App Model Understand the Aspire application model
Service Discovery Learn how service discovery works
Hosting Resources Explore available hosting integrations