Azure Container Apps — Serverless Containers on Microsoft Azure

Sanjeev SharmaSanjeev Sharma
5 min read

Advertisement

Introduction

Why This Matters

Azure Container Apps provides managed Kubernetes-based container hosting without exposing cluster complexity. Built on KEDA (Kubernetes Event-Driven Autoscaling) and Dapr (Distributed Application Runtime), it handles HTTP scaling, queue-driven scaling, service discovery, and distributed tracing out of the box. It sits between Azure App Service (simple PaaS) and AKS (full Kubernetes) in the complexity spectrum — ideal for microservices teams that need scale without ops burden.

Installation and Setup

# Install Azure CLI and Container Apps extension
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
az extension add --name containerapp --upgrade
 
# Login and set subscription
az login
az account set --subscription my-subscription-id
 
# Register required providers
az provider register --namespace Microsoft.App
az provider register --namespace Microsoft.OperationalInsights

Creating Environments and Apps

# Create resource group
az group create \
  --name myapp-rg \
  --location eastus
 
# Create Log Analytics workspace
az monitor log-analytics workspace create \
  --resource-group myapp-rg \
  --workspace-name myapp-logs
 
# Get workspace credentials
WORKSPACE_ID=$(az monitor log-analytics workspace show \
  --resource-group myapp-rg \
  --workspace-name myapp-logs \
  --query customerId --output tsv)
 
WORKSPACE_KEY=$(az monitor log-analytics workspace get-shared-keys \
  --resource-group myapp-rg \
  --workspace-name myapp-logs \
  --query primarySharedKey --output tsv)
 
# Create Container Apps environment
az containerapp env create \
  --name myapp-env \
  --resource-group myapp-rg \
  --location eastus \
  --logs-workspace-id $WORKSPACE_ID \
  --logs-workspace-key $WORKSPACE_KEY
 
# Deploy container app
az containerapp create \
  --name my-api \
  --resource-group myapp-rg \
  --environment myapp-env \
  --image myregistry.azurecr.io/my-api:v1.0 \
  --target-port 8080 \
  --ingress external \
  --min-replicas 1 \
  --max-replicas 20 \
  --cpu 0.5 \
  --memory 1Gi \
  --env-vars NODE_ENV=production LOG_LEVEL=info \
  --query properties.configuration.ingress.fqdn
 
# Update app with new image (creates new revision)
az containerapp update \
  --name my-api \
  --resource-group myapp-rg \
  --image myregistry.azurecr.io/my-api:v1.1

Revisions and Traffic Splitting

# List revisions
az containerapp revision list \
  --name my-api \
  --resource-group myapp-rg \
  --output table
 
# Enable multiple active revisions
az containerapp revision set-mode \
  --name my-api \
  --resource-group myapp-rg \
  --mode multiple
 
# Split traffic: 90% stable, 10% canary
az containerapp ingress traffic set \
  --name my-api \
  --resource-group myapp-rg \
  --revision-weight \
    my-api--v1=90 \
    my-api--v1-1=10
 
# Rollback: send all traffic to stable revision
az containerapp ingress traffic set \
  --name my-api \
  --resource-group myapp-rg \
  --revision-weight my-api--v1=100

Secrets Management

# Add secrets to container app
az containerapp secret set \
  --name my-api \
  --resource-group myapp-rg \
  --secrets db-password=MySecretPassword api-key=my-api-key-value
 
# Reference secrets as environment variables
az containerapp update \
  --name my-api \
  --resource-group myapp-rg \
  --set-env-vars \
    DB_PASSWORD=secretref:db-password \
    API_KEY=secretref:api-key
 
# Use Azure Key Vault references (recommended for production)
az containerapp secret set \
  --name my-api \
  --resource-group myapp-rg \
  --secrets kv-db-password=keyvaultref:https://mykeyvault.vault.azure.net/secrets/db-password,identityref:/subscriptions/.../managedIdentities/myapp-identity

KEDA Autoscaling

# Scale on HTTP requests (built-in)
az containerapp update \
  --name my-api \
  --resource-group myapp-rg \
  --min-replicas 0 \
  --max-replicas 30 \
  --scale-rule-name http-rule \
  --scale-rule-type http \
  --scale-rule-metadata concurrentRequests=50
 
# Scale on Azure Service Bus queue depth
az containerapp update \
  --name queue-processor \
  --resource-group myapp-rg \
  --min-replicas 0 \
  --max-replicas 10 \
  --scale-rule-name queue-rule \
  --scale-rule-type azure-servicebus \
  --scale-rule-metadata \
    queueName=orders \
    messageCount=5 \
  --scale-rule-auth \
    connection=connection-string-secret:servicebus-conn

Dapr Integration

# Enable Dapr sidecar on container app
az containerapp dapr enable \
  --name my-service \
  --resource-group myapp-rg \
  --dapr-app-id my-service \
  --dapr-app-port 8080 \
  --dapr-app-protocol http
 
# Service-to-service calls using Dapr (automatic service discovery)
# From my-service, call another-service:
# curl http://localhost:3500/v1.0/invoke/another-service/method/endpoint
 
# Dapr state store component
cat > statestore.yaml << 'EOF'
componentType: state.azure.blobstorage
version: v1
metadata:
  - name: accountName
    value: mystorageaccount
  - name: accountKey
    secretRef: storage-key
  - name: containerName
    value: state
EOF
 
az containerapp env dapr-component set \
  --name myapp-env \
  --resource-group myapp-rg \
  --dapr-component-name statestore \
  --yaml statestore.yaml

Private Registry Integration

# Link Azure Container Registry
az containerapp registry set \
  --name my-api \
  --resource-group myapp-rg \
  --server myregistry.azurecr.io \
  --identity system
 
# Grant AcrPull permission to the managed identity
ACR_ID=$(az acr show --name myregistry --query id --output tsv)
APP_IDENTITY=$(az containerapp show \
  --name my-api \
  --resource-group myapp-rg \
  --query identity.principalId --output tsv)
 
az role assignment create \
  --assignee $APP_IDENTITY \
  --role AcrPull \
  --scope $ACR_ID

Common Mistakes

  • Not setting --min-replicas 0 to save costs on non-production apps — scale-to-zero is the main cost benefit
  • Using the same Container Apps environment for production and dev — environments share networking and resources
  • Not enabling managed identity for registry authentication — avoid username/password registry credentials
  • Setting revision mode to single when doing canary deployments — you need multiple revision mode for traffic splitting
  • Ignoring replica health checks — without proper health probes, Container Apps may route traffic to unhealthy replicas

Best Practices

  • Use separate environments per stage (dev, staging, production) for network isolation
  • Enable system-assigned or user-assigned managed identities for accessing Key Vault and Container Registry without secrets
  • Set --min-replicas 1 for latency-sensitive production services to avoid cold starts
  • Use KEDA scaling rules tuned to your workload — HTTP concurrency for APIs, queue depth for workers
  • Monitor with Azure Monitor and configure alerts on replica count, CPU, memory, and request failure rates
  • Use Azure Container Registry with geo-replication if deploying to multiple regions

Key Takeaways

  • Azure Container Apps is built on Kubernetes, KEDA, and Dapr — managed abstractions hide cluster complexity
  • Environments are the networking boundary — apps in the same environment can communicate over a private VNET
  • Revisions are immutable snapshots — each container update creates a new revision enabling traffic splitting and rollback
  • KEDA provides event-driven autoscaling from zero — HTTP, queue depth, CPU, memory, and 50+ other triggers
  • Dapr integration provides service discovery, state management, pub/sub, and observability as sidecar containers
  • Secrets can reference Azure Key Vault directly — the preferred approach for production secret management
  • Scale-to-zero (--min-replicas 0) means you pay nothing when the app receives no traffic
  • Managed identity eliminates credential management for ACR, Key Vault, and Azure Storage access

Advertisement

Sanjeev Sharma

Written by

Sanjeev Sharma

Full Stack Engineer · E-mopro

Related reading