Azure Container Apps represents
Microsoft’s serverless container platform, offering Kubernetes-like capabilities without cluster management complexity, powered by KEDA auto-scaling and native Dapr integration.
Container Apps Architecture
Platform Comparison
Key Features
| Feature | Description | Use Case |
| Revisions | Immutable snapshots of app version | Blue-green, canary deployments |
| Traffic Splitting | Route % traffic to different revisions | A/B testing, gradual rollouts |
| KEDA Scaling | Event-driven auto-scale (HTTP, Queue) | Variable workloads, cost optimization |
| Dapr Integration | Service mesh, state, pub/sub | Microservices communication |
| Scale to Zero | 0 replicas when idle | Batch jobs, dev environments |
| Managed Identity | Azure AD authentication | Secure access to Azure services |
Deployment with Azure CLI
# Deploy Container App with Azure CLI
az containerapp create --name my-api --resource-group my-rg --environment my-env --image myregistry.azurecr.io/api:v1 --target-port 8080 --ingress external --min-replicas 0 --max-replicas 10 --cpu 0.5 --memory 1.0Gi --env-vars "DB_HOST=secretref:db-host" --secrets "db-host=mydb.postgres.database.azure.com" --registry-server myregistry.azurecr.io --registry-identity system
# KEDA HTTP scaling rule
az containerapp update --name my-api --resource-group my-rg --scale-rule-name http-rule --scale-rule-type http --scale-rule-http-concurrency 100
# KEDA Azure Queue scaling
az containerapp update --name my-worker --resource-group my-rg --scale-rule-name queue-rule --scale-rule-type azure-queue --scale-rule-metadata "queueName=jobs" "queueLength=5" "connectionFromEnv=STORAGE_CONNECTION"
Bicep Infrastructure
# Bicep: Container Apps Environment + App
param location string = resourceGroup().location
param envName string = 'my-env'
param appName string = 'my-api'
resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2021-06-01' = {
name: '${envName}-logs'
location: location
properties: {
sku: {
name: 'PerGB2018'
}
}
}
resource environment 'Microsoft.App/managedEnvironments@2023-05-01' = {
name: envName
location: location
properties: {
appLogsConfiguration: {
destination: 'log-analytics'
logAnalyticsConfiguration: {
customerId: logAnalytics.properties.customerId
sharedKey: logAnalytics.listKeys().primarySharedKey
}
}
daprAIInstrumentationKey: null
zoneRedundant: false
}
}
resource containerApp 'Microsoft.App/containerApps@2023-05-01' = {
name: appName
location: location
identity: {
type: 'SystemAssigned'
}
properties: {
environmentId: environment.id
configuration: {
ingress: {
external: true
targetPort: 8080
transport: 'auto'
traffic: [
{
latestRevision: true
weight: 100
}
]
}
dapr: {
enabled: true
appId: appName
appPort: 8080
}
secrets: [
{
name: 'db-password'
value: 'YourSecurePassword'
}
]
}
template: {
containers: [
{
name: 'api'
image: 'mcr.microsoft.com/azuredocs/containerapps-helloworld:latest'
resources: {
cpu: '0.5'
memory: '1.0Gi'
}
env: [
{
name: 'DB_PASSWORD'
secretRef: 'db-password'
}
]
}
]
scale: {
minReplicas: 0
maxReplicas: 10
rules: [
{
name: 'http-rule'
http: {
metadata: {
concurrentRequests: '100'
}
}
}
]
}
}
}
}
output fqdn string = containerApp.properties.configuration.ingress.fqdn
Traffic Splitting & Canary
# Traffic splitting: Blue-Green deployment
az containerapp revision copy --name my-api --resource-group my-rg --image myregistry.azurecr.io/api:v2
# Split traffic: 80% old, 20% new (canary)
az containerapp ingress traffic set --name my-api --resource-group my-rg --revision-weight my-api--v1=80 my-api--v2=20
# After validation, shift 100% to new
az containerapp ingress traffic set --name my-api --resource-group my-rg --revision-weight my-api--v2=100
# List all revisions
az containerapp revision list --name my-api --resource-group my-rg --output table
Dapr Configuration
# Enable Dapr state management
az containerapp dapr enable --name my-api --resource-group my-rg --dapr-app-id my-api --dapr-app-port 8080
# Create Dapr component (Redis state store)
az containerapp env dapr-component set --name my-env --resource-group my-rg --dapr-component-name statestore --yaml "
componentType: state.redis
version: v1
metadata:
- name: redisHost
value: myredis.redis.cache.windows.net:6380
- name: redisPassword
secretRef: redis-password
- name: enableTLS
value: true
scopes:
- my-api
"
KEDA Scaling Triggers
| Trigger | Metric | Example |
| HTTP | Concurrent requests | Scale at 100 requests/replica |
| Azure Queue | Queue length | Scale at 5 messages/replica |
| Azure Service Bus | Queue/topic length | Scale based on pending messages |
| Azure Event Hub | Lag in consumer group | Scale to process backlog |
| CPU | CPU percentage | Scale at 70% CPU utilization |
| Memory | Memory percentage | Scale at 80% memory usage |
| Custom | Any metric | Prometheus, AppInsights metrics |
Best Practices
- Use managed identities: Avoid storing secrets for Azure services
- Enable Dapr: Simplify microservices communication patterns
- Configure health probes: Startup, liveness, readiness checks
- Leverage traffic splitting: Safe deployments with canary releases
- Scale to zero: Optimize costs for variable workloads
- VNet integration: Secure communication with private endpoints
- Use Log Analytics: Centralized logging and monitoring
- Set resource limits: Prevent cost overruns with max replicas
- Multiple scaling rules: Combine HTTP + queue triggers
- Revision labels: Tag revisions for easier traffic management
Pricing Breakdown
- vCPU: $0.000012 per vCPU-second
- Memory: $0.0000013 per GB-second
- HTTP requests: First 2M free, then $0.40 per million
- Free tier: 180,000 vCPU-seconds + 360,000 GB-seconds/month
- Example (0.5 vCPU, 1GB, 24/7): ~$15-20/month
- Example (scaled to zero 80% of time): ~$3-4/month
References
Related
Discover more from C4: Container, Code, Cloud & Context
Subscribe to get the latest posts sent to your email.