Aspire Deployment Guide
This guide covers deploying the BookStore application using .NET Aspire to production environments, including Azure Container Apps and Kubernetes.
Overview
.NET Aspire separates the act of producing deployment assets from executing a deployment:
aspire publish- Generates intermediate, parameterized deployment artifacts (Docker Compose, Kubernetes manifests, Azure specifications)aspire deploy- Executes deployment by resolving parameters and applying changes to the target environment
The BookStore application uses Aspire to orchestrate:
- API Service - Event-sourced backend with Marten and PostgreSQL
- Web Frontend - Blazor application
- PostgreSQL - Database with event store and projections
- Azure Blob Storage - File storage (Azurite emulator locally)
Prerequisites
General Requirements
- .NET 10 SDK or later (with .NET Aspire workload)
- Docker Desktop or Podman (OCI-compliant container runtime)
- Aspire CLI (for publishing and deployment)
# Install Aspire workload
dotnet workload install aspire
# Verify installation
dotnet workload list
Azure Deployment Requirements
- Azure Developer CLI (azd) - Install azd
- Azure CLI - Configured and signed in
- Active Azure subscription with permissions to create resources
# Install Azure Developer CLI (macOS)
brew install azure-developer-cli
# Login to Azure
azd auth login
az login
Kubernetes Deployment Requirements
- kubectl - Kubernetes command-line tool
- Kubernetes cluster - AKS, EKS, GKE, or local (kind, minikube)
- Container registry - Azure Container Registry, Docker Hub, or private registry
- Aspire.Hosting.Kubernetes NuGet package
# Install kubectl (macOS)
brew install kubectl
# Verify cluster access
kubectl cluster-info
Deployment to Azure Container Apps
Azure Container Apps (ACA) is the recommended hosting environment for .NET Aspire applications, providing a fully managed, serverless platform for containerized microservices.
Step 1: Initialize Azure Deployment
From your solution root directory:
# Navigate to solution root
cd /path/to/BookStore
# Initialize azd
azd init
When prompted:
- Select "Use code in the current directory"
- Confirm the detected Aspire AppHost project (
BookStore.AppHost) - Enter an environment name (e.g.,
dev,staging,prod)
This generates:
azure.yaml- Defines services and Azure resource mappings.azure/config.json- Active environment configuration.azure/<environment>/.env- Environment-specific settings
Step 2: Configure Azure Resources
Review and customize azure.yaml if needed. The default configuration creates:
- Azure Container Apps Environment - Hosts all containers
- Azure Container Registry - Stores container images
- Log Analytics Workspace - Centralized logging
- Managed PostgreSQL - Production database
- Azure Storage Account - Blob storage
Important
The BookStore application requires PostgreSQL with pg_trgm and unaccent extensions. Ensure your Azure PostgreSQL Flexible Server has these extensions enabled.
Step 3: Deploy to Azure
Execute the provisioning and deployment:
azd up
This command:
- Packages services - Builds container images using .NET's built-in container publishing
- Provisions Azure resources - Creates resource group, container registry, container apps environment, etc.
- Pushes images - Uploads containers to Azure Container Registry
- Deploys services - Deploys containers to Azure Container Apps
When prompted:
- Select your Azure subscription
- Choose an Azure region (e.g.,
eastus2,westeurope) - Select which services to expose to the Internet (typically
webfrontend)
Step 4: Verify Deployment
After successful deployment, azd outputs:
- Service endpoints - URLs for your deployed services
- Aspire Dashboard URL - Monitoring and observability
- Resource group link - Azure Portal overview
# View deployment details
azd show
# Stream logs from a service
azd logs --service apiservice --follow
# Monitor all services
azd monitor
Step 5: Configure Database Extensions
Connect to your Azure PostgreSQL Flexible Server and enable required extensions:
-- Connect to your database
\c bookstore
-- Enable extensions
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE EXTENSION IF NOT EXISTS unaccent;
Step 6: Run Database Migrations
The BookStore application uses Marten for event sourcing. Initialize the database schema:
# Option 1: Use the API's built-in migration endpoint (if exposed)
curl -X POST https://<apiservice-url>/api/admin/projections/rebuild
# Option 2: Run migrations locally against Azure database
# Set connection string in user secrets or environment variable
dotnet user-secrets set "ConnectionStrings:bookstore" "<azure-connection-string>" \
--project src/BookStore.ApiService
# Run the API service to apply migrations
dotnet run --project src/BookStore.ApiService
Managing Azure Deployments
# Update existing deployment
azd deploy
# Provision infrastructure only
azd provision
# Clean up all resources
azd down
# View environment variables
azd env get-values
Cost Optimization
Azure Container Apps uses consumption-based pricing. To minimize costs:
- Scale to zero - Configure min replicas to 0 for non-production environments
- Use Azure PostgreSQL Basic tier - For development/staging
- Enable auto-pause - For development databases
- Use resource tags - Track costs by environment
Deployment to Kubernetes
Kubernetes deployment provides maximum flexibility and portability across cloud providers and on-premises infrastructure.
Step 1: Add Kubernetes Hosting Integration
Add the Kubernetes hosting package to your AppHost project:
dotnet add src/BookStore.AppHost/BookStore.AppHost.csproj \
package Aspire.Hosting.Kubernetes
Step 2: Generate Kubernetes Manifests
Use the Aspire CLI to generate Kubernetes YAML manifests:
# Generate manifests to output directory
aspire publish -o ./k8s-artifacts
# Review generated files
ls -la ./k8s-artifacts
This generates:
- Deployments - For API service and web frontend
- StatefulSets - For PostgreSQL (if using in-cluster database)
- Services - Internal service discovery
- ConfigMaps - Application configuration
- Secrets - Sensitive data (connection strings, API keys)
- PersistentVolumeClaims - For PostgreSQL data
- Ingress - External access (if configured)
Step 3: Configure Container Registry
Build and push container images to your registry:
# Set your container registry
export CONTAINER_REGISTRY="myregistry.azurecr.io"
# Login to registry
# Azure Container Registry
az acr login --name myregistry
# Docker Hub
docker login
# Build and push API service
docker build -t $CONTAINER_REGISTRY/bookstore-api:latest \
-f src/BookStore.ApiService/Dockerfile .
docker push $CONTAINER_REGISTRY/bookstore-api:latest
# Build and push web frontend
docker build -t $CONTAINER_REGISTRY/bookstore-web:latest \
-f src/BookStore.Web/Dockerfile .
docker push $CONTAINER_REGISTRY/bookstore-web:latest
Tip
For Azure Container Registry, grant your AKS cluster pull permissions:
az aks update -n <cluster-name> -g <resource-group> \
--attach-acr <registry-name>
Step 4: Configure Secrets and ConfigMaps
Create Kubernetes secrets for sensitive data:
# Create namespace
kubectl create namespace bookstore
# PostgreSQL connection string
kubectl create secret generic bookstore-db \
--from-literal=connectionString="Host=postgres;Database=bookstore;Username=postgres;Password=<password>" \
-n bookstore
# Azure Storage connection string (for production)
kubectl create secret generic bookstore-storage \
--from-literal=connectionString="<azure-storage-connection-string>" \
-n bookstore
Step 5: Deploy PostgreSQL
For production, use a managed database service (Azure Database for PostgreSQL, AWS RDS, Google Cloud SQL). For development/testing, deploy PostgreSQL in-cluster:
# postgres-deployment.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
namespace: bookstore
spec:
serviceName: postgres
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:16
env:
- name: POSTGRES_DB
value: bookstore
- name: POSTGRES_USER
value: postgres
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: bookstore-db
key: password
ports:
- containerPort: 5432
volumeMounts:
- name: postgres-data
mountPath: /var/lib/postgresql/data
volumeClaimTemplates:
- metadata:
name: postgres-data
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 10Gi
---
apiVersion: v1
kind: Service
metadata:
name: postgres
namespace: bookstore
spec:
selector:
app: postgres
ports:
- port: 5432
targetPort: 5432
clusterIP: None
Apply the PostgreSQL deployment:
kubectl apply -f postgres-deployment.yaml
Step 6: Deploy Application Services
Update the generated manifests with your container registry and apply:
# Update image references in manifests
sed -i '' "s|image: .*bookstore-api.*|image: $CONTAINER_REGISTRY/bookstore-api:latest|g" \
k8s-artifacts/*.yaml
sed -i '' "s|image: .*bookstore-web.*|image: $CONTAINER_REGISTRY/bookstore-web:latest|g" \
k8s-artifacts/*.yaml
# Apply all manifests
kubectl apply -f k8s-artifacts/ -n bookstore
# Verify deployments
kubectl get pods -n bookstore
kubectl get services -n bookstore
Step 7: Configure Ingress
Expose the web frontend using an Ingress controller:
# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: bookstore-ingress
namespace: bookstore
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
ingressClassName: nginx
tls:
- hosts:
- bookstore.example.com
secretName: bookstore-tls
rules:
- host: bookstore.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: webfrontend
port:
number: 80
Apply the ingress:
kubectl apply -f ingress.yaml
Step 8: Verify Deployment
# Check pod status
kubectl get pods -n bookstore -w
# View logs
kubectl logs -f deployment/apiservice -n bookstore
kubectl logs -f deployment/webfrontend -n bookstore
# Port-forward for local testing
kubectl port-forward svc/webfrontend 8080:80 -n bookstore
# Check ingress
kubectl get ingress -n bookstore
Managing Kubernetes Deployments
# Update deployment with new image
kubectl set image deployment/apiservice \
apiservice=$CONTAINER_REGISTRY/bookstore-api:v2 -n bookstore
# Scale deployment
kubectl scale deployment/apiservice --replicas=3 -n bookstore
# Rollback deployment
kubectl rollout undo deployment/apiservice -n bookstore
# View rollout history
kubectl rollout history deployment/apiservice -n bookstore
# Delete all resources
kubectl delete namespace bookstore
Azure Kubernetes Service (AKS) Deployment
For deploying to Azure Kubernetes Service, combine both approaches:
Step 1: Create AKS Cluster
# Set variables
RESOURCE_GROUP="bookstore-rg"
CLUSTER_NAME="bookstore-aks"
LOCATION="eastus2"
ACR_NAME="bookstoreacr"
# Create resource group
az group create --name $RESOURCE_GROUP --location $LOCATION
# Create Azure Container Registry
az acr create --resource-group $RESOURCE_GROUP \
--name $ACR_NAME --sku Basic
# Create AKS cluster
az aks create \
--resource-group $RESOURCE_GROUP \
--name $CLUSTER_NAME \
--node-count 2 \
--enable-managed-identity \
--attach-acr $ACR_NAME \
--generate-ssh-keys
# Get credentials
az aks get-credentials --resource-group $RESOURCE_GROUP --name $CLUSTER_NAME
Step 2: Create Azure Database for PostgreSQL
# Create PostgreSQL Flexible Server
az postgres flexible-server create \
--resource-group $RESOURCE_GROUP \
--name bookstore-db \
--location $LOCATION \
--admin-user dbadmin \
--admin-password '<secure-password>' \
--sku-name Standard_B1ms \
--tier Burstable \
--version 16 \
--storage-size 32
# Create database
az postgres flexible-server db create \
--resource-group $RESOURCE_GROUP \
--server-name bookstore-db \
--database-name bookstore
# Configure firewall (allow Azure services)
az postgres flexible-server firewall-rule create \
--resource-group $RESOURCE_GROUP \
--name bookstore-db \
--rule-name AllowAzureServices \
--start-ip-address 0.0.0.0 \
--end-ip-address 0.0.0.0
Step 3: Deploy Application
Follow the Kubernetes deployment steps above, using Azure Container Registry and Azure Database for PostgreSQL connection strings.
Environment Configuration
Development Environment
Use Aspire's local development experience:
aspire run
For a detailed look at the local orchestration configuration, see the Aspire Orchestration Guide.
This starts:
- Aspire Dashboard
- All services with hot reload
- Azurite (Azure Storage emulator)
- PostgreSQL container
- PgAdmin
Staging/Production Environments
Use environment-specific configuration:
# Azure
azd env new staging
azd env select staging
azd up
# Kubernetes
kubectl create namespace bookstore-staging
kubectl apply -f k8s-artifacts/ -n bookstore-staging
Configuration Management
Azure Container Apps:
- Use Azure App Configuration for centralized settings
- Use Azure Key Vault for secrets
- Configure via
azdenvironment variables
Kubernetes:
- Use ConfigMaps for non-sensitive configuration
- Use Secrets for sensitive data
- Consider external secret management (Azure Key Vault, HashiCorp Vault)
Monitoring and Observability
Aspire Dashboard
Deploy the Aspire Dashboard to production for monitoring:
# Azure Container Apps (included by default with azd)
# Access via the URL provided by azd up
# Kubernetes
kubectl apply -f https://raw.githubusercontent.com/dotnet/aspire/main/src/Aspire.Dashboard/kubernetes/aspire-dashboard.yaml
kubectl port-forward svc/aspire-dashboard 18888:18888 -n aspire-system
Application Insights (Azure)
Configure Application Insights for production telemetry:
# Create Application Insights
az monitor app-insights component create \
--app bookstore-insights \
--location $LOCATION \
--resource-group $RESOURCE_GROUP
# Get instrumentation key
az monitor app-insights component show \
--app bookstore-insights \
--resource-group $RESOURCE_GROUP \
--query instrumentationKey
Add to your services:
// In Program.cs
builder.Services.AddApplicationInsightsTelemetry(
builder.Configuration["ApplicationInsights:InstrumentationKey"]);
Health Checks
Both services expose health check endpoints:
# API Service
curl https://<api-url>/health
# Web Frontend
curl https://<web-url>/health
Configure Kubernetes liveness and readiness probes:
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
Troubleshooting
Azure Container Apps
# View logs
azd logs --service apiservice --follow
# Check container status
az containerapp show \
--name apiservice \
--resource-group <resource-group>
# View revisions
az containerapp revision list \
--name apiservice \
--resource-group <resource-group>
Kubernetes
# Check pod status
kubectl describe pod <pod-name> -n bookstore
# View logs
kubectl logs <pod-name> -n bookstore --tail=100 -f
# Execute commands in pod
kubectl exec -it <pod-name> -n bookstore -- /bin/bash
# Check events
kubectl get events -n bookstore --sort-by='.lastTimestamp'
Common Issues
Database Connection Failures:
- Verify connection strings in secrets
- Check firewall rules (Azure)
- Ensure PostgreSQL extensions are enabled
Image Pull Errors:
- Verify container registry authentication
- Check image names and tags
- Ensure AKS has ACR pull permissions (Azure)
Service Discovery Issues:
- Verify service names match configuration
- Check DNS resolution in pods
- Review Aspire service references
CI/CD Integration
GitHub Actions (Azure)
# .github/workflows/deploy-azure.yml
name: Deploy to Azure
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'
- name: Install azd
uses: Azure/setup-azd@v1
- name: Login to Azure
uses: azure/login@v2
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Deploy to Azure
run: azd up --no-prompt
env:
AZURE_ENV_NAME: production
GitHub Actions (Kubernetes)
# .github/workflows/deploy-k8s.yml
name: Deploy to Kubernetes
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Login to ACR
uses: azure/docker-login@v2
with:
login-server: ${{ secrets.ACR_LOGIN_SERVER }}
username: ${{ secrets.ACR_USERNAME }}
password: ${{ secrets.ACR_PASSWORD }}
- name: Build and push images
run: |
docker build -t ${{ secrets.ACR_LOGIN_SERVER }}/bookstore-api:${{ github.sha }} \
-f src/BookStore.ApiService/Dockerfile .
docker push ${{ secrets.ACR_LOGIN_SERVER }}/bookstore-api:${{ github.sha }}
docker build -t ${{ secrets.ACR_LOGIN_SERVER }}/bookstore-web:${{ github.sha }} \
-f src/BookStore.Web/Dockerfile .
docker push ${{ secrets.ACR_LOGIN_SERVER }}/bookstore-web:${{ github.sha }}
- name: Set up kubectl
uses: azure/setup-kubectl@v4
- name: Deploy to AKS
uses: azure/k8s-deploy@v5
with:
manifests: |
k8s-artifacts/
images: |
${{ secrets.ACR_LOGIN_SERVER }}/bookstore-api:${{ github.sha }}
${{ secrets.ACR_LOGIN_SERVER }}/bookstore-web:${{ github.sha }}
namespace: bookstore
Security Best Practices
- Use Managed Identities - Avoid storing credentials (Azure)
- Enable HTTPS - Use TLS certificates (Let's Encrypt, Azure-managed)
- Network Policies - Restrict pod-to-pod communication (Kubernetes)
- Secret Management - Use Azure Key Vault or external secret stores
- RBAC - Configure role-based access control
- Container Scanning - Scan images for vulnerabilities
- Regular Updates - Keep dependencies and base images updated