Home / Azure / Azure Container Apps: Serverless Containers Made Simple (2025-05-05)
Azure

Azure Container Apps: Serverless Containers Made Simple (2025-05-05)

Deploy containerized applications with Azure Container Apps—serverless scaling with KEDA, Dapr integration, and simplified Kubernetes alternative for microse...

What you will learn

Practical execution with concise explanations, real implementation patterns, and production-ready recommendations.

Azure Container Apps: Serverless Containers Made Simple

RUN dotnet restore COPY . . RUN dotnet build -c Release -o /app/build

FROM build AS publish RUN dotnet publish -c Release -o /app/publish

FROM base AS final WORKDIR /app COPY --from=publish /app/publish . ENTRYPOINT ["dotnet", "MyApi.dll"]


**Build and Push:**

```bash
az acr create --resource-group rg-containerapp --name contosoregistry --sku Basic

az acr build --registry contosoregistry --image myapi:v1 .

Expected output:

{ "loginServer": "acrmyapp.azurecr.io", "provisioningState": "Succeeded" }

Terminal output for az acr create

Step 3: Deploy Container App

az containerapp create \
  --name api-orders \
  --resource-group rg-containerapp \
  --environment env-production \
  --image contosoregistry.azurecr.io/myapi:v1 \
  --registry-server contosoregistry.azurecr.io \
  --registry-identity system \
  --target-port 80 \
  --ingress external \
  --min-replicas 1 \
  --max-replicas 10 \
  --cpu 0.5 --memory 1Gi \




  --env-vars "ASPNETCORE_ENVIRONMENT=Production" "ConnectionStrings__Database=secretref:db-connection"


Expected output:

{ "name": "myapp", "properties": { "runningStatus": "Running", "provisioningState": "Succeeded" } }

Terminal output for az containerapp create

Step 4: Configure Autoscaling with KEDA

HTTP Scaling Rule:

az containerapp update \
  --name api-orders \
  --resource-group rg-containerapp \
  --scale-rule-name http-rule \
  --scale-rule-type http \
  --scale-rule-http-concurrency 50

Azure Queue Scaling Rule:

az containerapp update \
  --name processor-jobs \
  --resource-group rg-containerapp \
  --scale-rule-name queue-rule \
  --scale-rule-type azure-queue \
  --scale-rule-metadata queueName=orders accountName=contosostore queueLength=10 \
  --scale-rule-auth triggerParameter=connection secretRef=storage-connection

Custom KEDA Scaler (CPU):

## containerapp.yaml
properties:
  template:
```yaml
scale:
  minReplicas: 0
  maxReplicas: 30
  rules:
  - name: cpu-scaling
    custom:
      type: cpu
      metadata:
        type: Utilization
        value: "70"

## Step 5: Traffic Splitting (Blue-Green/Canary)

**Deploy New Revision:**





```bash
az containerapp update \
  --name api-orders \
  --resource-group rg-containerapp \
  --image contosoregistry.azurecr.io/myapi:v2 \
  --revision-suffix v2

Split Traffic:

az containerapp ingress traffic set \
  --name api-orders \
  --resource-group rg-containerapp \
  --revision-weight api-orders--v1=80 api-orders--v2=20

Gradually Shift to 100% v2:

az containerapp ingress traffic set \
  --name api-orders \
  --resource-group rg-containerapp \
  --revision-weight api-orders--v2=100

Step 6: Dapr Integration for Microservices

Enable Dapr:

az containerapp dapr enable \
  --name api-orders \
  --resource-group rg-containerapp \
  --dapr-app-id orders \
  --dapr-app-port 80

Service-to-Service Invocation:

// C# - Dapr SDK
var daprClient = new DaprClientBuilder().Build();

// Call another container app
var inventory = await daprClient.InvokeMethodAsync<InventoryResponse>(
```text
HttpMethod.Get,
"inventory-service",
"api/items/123"```
);

Pub/Sub Pattern:

## Create Dapr component (Azure Service Bus)
az containerapp env dapr-component set \
  --name env-production \
  --resource-group rg-containerapp \
  --dapr-component-name pubsub \
  --yaml "
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: pubsub
spec:
  type: pubsub.azure.servicebus
  metadata:
  - name: connectionString
```yaml




secretRef: servicebus-connection```
"

// Publish event
await daprClient.PublishEventAsync("pubsub", "orders", new OrderCreatedEvent
{
```text
OrderId = "12345",
Total = 99.99m```
});

// Subscribe to event
[Topic("pubsub", "orders")]
[HttpPost("orders")]
public async Task<IActionResult> HandleOrder(OrderCreatedEvent order)
{
```text
// Process order
return Ok();```
}

Step 7: Secrets Management

Add Secrets:

az containerapp secret set \
  --name api-orders \
  --resource-group rg-containerapp \
  --secrets "db-connection=Server=...;Database=..." "api-key=secret123"

Reference in Environment Variables:

az containerapp update \
  --name api-orders \
  --resource-group rg-containerapp \
  --set-env-vars "DatabaseConnection=secretref:db-connection" "ApiKey=secretref:api-key"

Azure Key Vault Integration:

az containerapp env dapr-component set \
  --name env-production \
  --resource-group rg-containerapp \
  --dapr-component-name secretstore \
  --yaml "
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: secretstore
spec:
  type: secretstores.azure.keyvault
  metadata:
  - name: vaultName
```yaml
value: contoso-keyvault```
  - name: azureClientId
```yaml
value: <managed-identity-client-id>```
"

Step 8: Observability

Application Insights:

az containerapp update \
  --name api-orders \
  --resource-group rg-containerapp \
  --set-env-vars "APPLICATIONINSIGHTS_CONNECTION_STRING=InstrumentationKey=..."

Log Analytics Queries:

ContainerAppConsoleLogs_CL
| where ContainerAppName_s == "api-orders"
| where Level_s == "Error"
| project TimeGenerated, Message_s
| order by TimeGenerated desc

Metrics Monitoring:

ContainerAppSystemLogs_CL
| where ContainerAppName_s == "api-orders"
| summarize AvgReplicas = avg(ReplicaCount_d) by bin(TimeGenerated, 5m)
| render timechart

Advanced Patterns

Pattern 1: Background Jobs with Queue Trigger

az containerapp create \
  --name job-processor \
  --resource-group rg-containerapp \
  --environment env-production \
  --image contosoregistry.azurecr.io/processor:v1 \
  --min-replicas 0 \
  --max-replicas 50 \
  --scale-rule-name queue-scaling \
  --scale-rule-type azure-queue \
  --scale-rule-metadata queueName=tasks accountName=contosostore queueLength=5

Expected output:

{ "name": "myapp", "properties": { "runningStatus": "Running", "provisioningState": "Succeeded" } }

Terminal output for az containerapp create

Pattern 2: Scheduled Jobs (Cron)

az containerapp job create \
  --name nightly-report \
  --resource-group rg-containerapp \
  --environment env-production \
  --image contosoregistry.azurecr.io/reports:v1 \
  --trigger-type Schedule \
  --cron-expression "0 2 * * *" \
  --parallelism 1 \
  --replica-completion-count 1

Pattern 3: Internal Communication (No Public Ingress)

az containerapp create \
  --name internal-api \
  --resource-group rg-containerapp \
  --environment env-production \
  --image contosoregistry.azurecr.io/internal:v1 \
  --target-port 80 \
  --ingress internal \
  --min-replicas 2

Expected output:

{ "name": "myapp", "properties": { "runningStatus": "Running", "provisioningState": "Succeeded" } }

Terminal output for az containerapp create

Pattern 4: Custom Domain with TLS

az containerapp hostname add \
  --name api-orders \
  --resource-group rg-containerapp \
  --hostname api.contoso.com

az containerapp hostname bind \
  --name api-orders \
  --resource-group rg-containerapp \
  --hostname api.contoso.com \
  --environment env-production \
  --validation-method CNAME

Cost Optimization

Strategy Savings Implementation
Scale to zero 100% idle cost Set minReplicas=0 for infrequent workloads
Right-size CPU/memory 30-50% Use 0.25 vCPU / 0.5 Gi for lightweight APIs
Spot instances N/A Not available (already consumption-based)
Commitment discounts Up to 15% Reserved capacity for predictable workloads

Pricing Model:

  • vCPU: $0.000012/second ($0.043/hour)
  • Memory: $0.0000013/GB/second ($0.0047/GB/hour)
  • Requests: First 2M free, then $0.40/million

Comparison: Container Apps vs AKS vs App Service

Feature Container Apps AKS App Service
Complexity Low High Low
Control Medium Full Limited
Scaling KEDA (event-driven) HPA/KEDA Built-in
Pricing Consumption Node-based Plan-based
Best For Microservices, APIs, jobs Complex orchestration Web apps

Troubleshooting

Issue: Container fails to start
Solution: Check logs via az containerapp logs show; verify container health probe; ensure image exists in registry

Issue: Ingress not reachable
Solution: Confirm ingress type (external/internal); check NSG rules; verify DNS propagation for custom domains

Issue: Scaling not triggering
Solution: Validate KEDA scaler metadata; check authentication for queue/event sources; review replica metrics

Best Practices

  • Use managed identities for Azure resource authentication
  • Enable Dapr for cross-service communication patterns
  • Set appropriate min/max replicas based on load patterns
  • Implement health probes (readiness/liveness)
  • Use secrets for sensitive configuration
  • Monitor with Application Insights + Log Analytics
  • Version container images with immutable tags

Architecture Decision and Tradeoffs

When designing cloud infrastructure solutions with Azure, consider these key architectural trade-offs:

Approach Best For Tradeoff
Managed / platform service Rapid delivery, reduced ops burden Less customisation, potential vendor lock-in
Custom / self-hosted Full control, advanced tuning Higher operational overhead and cost

Recommendation: Start with the managed approach for most workloads and move to custom only when specific requirements demand it.

Validation and Versioning

  • Last validated: April 2026
  • Validate examples against your tenant, region, and SKU constraints before production rollout.
  • Keep module, CLI, and SDK versions pinned in automation pipelines and review quarterly.

Security and Governance Considerations

  • Apply least-privilege access using RBAC roles and just-in-time elevation for admin tasks.
  • Store secrets in managed secret stores and avoid embedding credentials in scripts or source files.
  • Enable audit logging, data protection policies, and periodic access reviews for regulated workloads.

Cost and Performance Notes

  • Define budgets and alerts, then monitor usage and cost trends continuously after go-live.
  • Baseline performance with synthetic and real-user checks before and after major changes.
  • Scale resources with measured thresholds and revisit sizing after usage pattern changes.

Official Microsoft References

  • https://learn.microsoft.com/azure/
  • https://learn.microsoft.com/azure/architecture/
  • https://learn.microsoft.com/azure/well-architected/

Public Examples from Official Sources

  • These examples are sourced from official public Microsoft documentation and sample repositories.
  • Documentation examples: https://learn.microsoft.com/azure/architecture/
  • Sample repositories: https://github.com/Azure-Samples
  • Prefer adapting these examples to your tenant, subscriptions, and governance requirements before production use.

Key Takeaways

  • Container Apps = Serverless + Containers without Kubernetes complexity.
  • KEDA enables event-driven autoscaling (queues, HTTP, custom).
  • Dapr simplifies microservices patterns (pub/sub, state, secrets).
  • Traffic splitting supports zero-downtime deployments.

Next Steps

  • Implement circuit breaker with Dapr resiliency policies
  • Add Azure Front Door for global load balancing
  • Explore Azure Container Apps jobs for batch processing

Additional Resources


Ready to simplify your container deployments?

Discussion