Azure Container Apps for .NET: Serverless Containers in Production
Deploy .NET microservices to Azure Container Apps. Covers environments, revisions, scaling rules, Dapr integration, secrets, managed identity, KEDA, blue-green deployments, and cost optimisation.
What is Azure Container Apps?
Azure Container Apps (ACA) is a serverless container platform built on Kubernetes ā but you never touch Kubernetes directly. It manages scaling, networking, certificates, and Dapr integration for you.
Azure Container Apps
āāā Environment (shared VNet, Log Analytics)
ā āāā Container App: orders-api
ā ā āāā Revision 1 (previous) ā 20% traffic
ā ā āāā Revision 2 (current) ā 80% traffic
ā āāā Container App: product-api
ā āāā Container App: notificationsACA vs AKS vs App Service:
| | App Service | ACA | AKS | |---|---|---|---| | Control | Low | Medium | Full | | K8s expertise needed | None | None | Yes | | Scale to zero | No | Yes | Configurable | | Dapr built-in | No | Yes | Manual | | Cost | Always-on | Pay per use | Node VMs | | Use when | Simple web apps | Microservices | Full K8s control |
Core Concepts
Environment ā shared boundary for all your apps. Shared VNet, Log Analytics workspace, Dapr configuration.
Revision ā an immutable snapshot of a container app. Every deployment creates a new revision. Traffic splits between revisions for blue-green.
Replica ā a running instance of a revision. ACA scales replicas based on rules.
Ingress ā HTTP/HTTPS routing. Can be external (public) or internal (VNet only).
Deploy with Azure CLI
# Create resource group and environment
az group create --name orderflow-rg --location uksouth
az containerapp env create \
--name orderflow-env \
--resource-group orderflow-rg \
--location uksouth
# Deploy a container app
az containerapp create \
--name orders-api \
--resource-group orderflow-rg \
--environment orderflow-env \
--image myregistry.azurecr.io/orders-api:latest \
--target-port 8080 \
--ingress external \
--min-replicas 1 \
--max-replicas 10 \
--cpu 0.5 \
--memory 1.0Gi \
--registry-server myregistry.azurecr.io \
--registry-identity system
# Update to a new image (creates new revision)
az containerapp update \
--name orders-api \
--resource-group orderflow-rg \
--image myregistry.azurecr.io/orders-api:v2Bicep / Infrastructure as Code
// main.bicep
resource environment 'Microsoft.App/managedEnvironments@2024-03-01' = {
name: 'orderflow-env'
location: location
properties: {
appLogsConfiguration: {
destination: 'log-analytics'
logAnalyticsConfiguration: {
customerId: logAnalytics.properties.customerId
sharedKey: logAnalytics.listKeys().primarySharedKey
}
}
}
}
resource ordersApi 'Microsoft.App/containerApps@2024-03-01' = {
name: 'orders-api'
location: location
identity: { type: 'SystemAssigned' }
properties: {
environmentId: environment.id
configuration: {
ingress: {
external: true
targetPort: 8080
transport: 'http2'
}
secrets: [
{ name: 'db-connection', value: sqlConnectionString }
]
registries: [
{ server: acrLoginServer, identity: 'system' }
]
}
template: {
containers: [
{
name: 'orders-api'
image: '${acrLoginServer}/orders-api:${imageTag}'
resources: { cpu: '0.5', memory: '1Gi' }
env: [
{ name: 'ASPNETCORE_ENVIRONMENT', value: 'Production' }
{ name: 'ConnectionStrings__Default', secretRef: 'db-connection' }
]
}
]
scale: {
minReplicas: 1
maxReplicas: 20
rules: [
{
name: 'http-scale'
http: { metadata: { concurrentRequests: '100' } }
}
]
}
}
}
}Scaling Rules
ACA uses KEDA under the hood. Scale based on HTTP traffic, queue depth, CPU, memory, or custom metrics.
# HTTP-based scaling
az containerapp update \
--name orders-api \
--scale-rule-name http-rule \
--scale-rule-type http \
--scale-rule-metadata concurrentRequests=50
# Azure Service Bus queue depth
az containerapp update \
--name notification-worker \
--scale-rule-name queue-rule \
--scale-rule-type azure-servicebus \
--scale-rule-metadata queueName=notifications messageCount=10 \
--scale-rule-auth triggerAuthentication=sb-authScale to zero (pay nothing when idle):
az containerapp update \
--name batch-processor \
--min-replicas 0 \
--max-replicas 5Managed Identity (No Credentials)
// Use DefaultAzureCredential ā works locally (dev identity) and in ACA (managed identity)
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(new SqlConnection
{
ConnectionString = configuration.GetConnectionString("Default"),
AccessToken = new DefaultAzureCredential()
.GetToken(new TokenRequestContext(["https://database.windows.net/.default"]))
.Token
}));
// Or for Key Vault
builder.Configuration.AddAzureKeyVault(
new Uri($"https://{kvName}.vault.azure.net/"),
new DefaultAzureCredential());Grant access in ACA:
# Assign Key Vault Secrets User role to the container app's managed identity
az role assignment create \
--role "Key Vault Secrets User" \
--assignee $(az containerapp show --name orders-api --query identity.principalId -o tsv) \
--scope /subscriptions/.../resourceGroups/orderflow-rg/providers/Microsoft.KeyVault/vaults/my-kvBlue-Green Deployments
# Deploy new version ā traffic still goes to old revision
az containerapp update \
--name orders-api \
--image myregistry.azurecr.io/orders-api:v2 \
--revision-suffix v2 \
--traffic-weight latest=0 # new revision gets 0% traffic
# Gradually shift traffic
az containerapp ingress traffic set \
--name orders-api \
--revision-weight orders-api--v2=20 orders-api--v1=80
# Full cutover
az containerapp ingress traffic set \
--name orders-api \
--revision-weight orders-api--v2=100
# Rollback instantly ā just point traffic back
az containerapp ingress traffic set \
--name orders-api \
--revision-weight orders-api--v1=100Built-in Dapr Integration
No separate Dapr installation needed ā ACA manages the sidecar:
az containerapp dapr enable \
--name orders-api \
--dapr-app-id orders-api \
--dapr-app-port 8080 \
--dapr-app-protocol http// In Bicep
dapr: {
enabled: true
appId: 'orders-api'
appPort: 8080
appProtocol: 'http'
}GitHub Actions Deployment
- name: Deploy to Azure Container Apps
uses: azure/container-apps-deploy-action@v1
with:
acrName: ${{ vars.ACR_NAME }}
containerAppName: orders-api
resourceGroup: orderflow-rg
imageToDeploy: ${{ vars.ACR_LOGIN_SERVER }}/orders-api:${{ github.sha }}
environmentVariables: |
ASPNETCORE_ENVIRONMENT=ProductionHealth Probes
// Startup, liveness, and readiness probes
builder.Services.AddHealthChecks()
.AddSqlServer(connectionString, tags: ["ready"])
.AddRedis(redisConnection, tags: ["ready"]);
app.MapHealthChecks("/health/live"); // liveness ā is the process alive?
app.MapHealthChecks("/health/ready", // readiness ā is it ready to serve traffic?
new HealthCheckOptions { Predicate = c => c.Tags.Contains("ready") });ACA uses these automatically if you configure them in the revision.
Interview Questions
Q: What is the difference between Azure Container Apps and Azure Kubernetes Service? ACA is a managed serverless container platform ā you don't manage nodes, networking, or Kubernetes control plane. AKS gives you full Kubernetes control but requires operational expertise. Use ACA when you want containers without K8s complexity; use AKS when you need advanced networking, custom admission controllers, or specific node configurations.
Q: What is a revision in Azure Container Apps? An immutable snapshot created on every deployment. Revisions can receive a share of traffic ā enabling blue-green and canary deployments without additional tooling. Rolling back means redirecting traffic to a previous revision ā instant with no redeployment.
Q: How does ACA scale to zero and why is that useful? When min replicas is set to 0, ACA terminates all instances when there's no traffic. You pay nothing for idle time. When traffic arrives, ACA starts a replica (cold start ~1-2s for .NET). Useful for batch processors, scheduled jobs, and low-traffic services in dev/staging.
Q: How do you avoid hardcoded credentials in Azure Container Apps?
Assign a system-managed identity to the container app, then grant it RBAC roles on Key Vault, SQL Server, Service Bus, etc. Use DefaultAzureCredential in code ā it automatically uses the managed identity in ACA and your developer identity locally. No secrets, no rotation.
Enjoyed this article?
Explore the Backend Systems learning path for more.
Found this helpful?
Leave a comment
Have a question, correction, or just found this helpful? Leave a note below.