Azure Container Apps — Run Production Containers Without Kubernetes

Posted on: 4/17/2026 5:05:43 PM

1. What is Azure Container Apps?

Azure Container Apps (ACA) is Microsoft Azure's serverless container platform that lets you run containerized applications without managing a Kubernetes cluster, node pools, or any underlying infrastructure. ACA automatically handles scaling, load balancing, TLS certificates, and revision management — you focus on code.

If you've ever felt Kubernetes is overkill for a backend API or a handful of microservices, ACA is exactly what you need. It delivers the power of container orchestration without requiring you to understand etcd, kube-proxy, or CNI plugins.

180,000 Free vCPU-seconds/month
360,000 Free GiB-seconds/month
2 million Free requests/month
0 → N Automatic scale-to-zero

Why not use Kubernetes directly?

AKS (Azure Kubernetes Service) is powerful when you need full control of the cluster. But for most web apps, APIs, and microservices, ACA provides 90% of the features you need with only 10% of the management effort. You don't have to think about node upgrades, cluster networking, or RBAC policies — Azure handles it.

2. System architecture

ACA is built on top of Kubernetes (AKS) but fully abstracts that layer from developers. The architecture has three main tiers:

graph TB
    subgraph Internet
        U[Users / Clients]
    end
    subgraph ACA_ENV["Container Apps Environment"]
        direction TB
        INGRESS["Built-in Ingress
TLS + Load Balancer"] subgraph APPS["Container Apps"] A1["API Gateway
.NET 10"] A2["Order Service
.NET 10"] A3["Notification
Worker"] end subgraph INFRA["Platform Services"] DAPR["Dapr Sidecar"] KEDA["KEDA Autoscaler"] ENVOY["Envoy Proxy"] end subgraph STORAGE["Managed Storage"] LOG["Log Analytics"] SECRET["Secret Store"] end end U --> INGRESS INGRESS --> A1 A1 --> DAPR DAPR --> A2 DAPR --> A3 A2 --> KEDA A3 --> KEDA APPS --> LOG APPS --> SECRET style ACA_ENV fill:#f8f9fa,stroke:#e94560,color:#2c3e50 style INGRESS fill:#e94560,stroke:#fff,color:#fff style DAPR fill:#2c3e50,stroke:#fff,color:#fff style KEDA fill:#2c3e50,stroke:#fff,color:#fff style ENVOY fill:#2c3e50,stroke:#fff,color:#fff style A1 fill:#fff,stroke:#e94560,color:#2c3e50 style A2 fill:#fff,stroke:#e94560,color:#2c3e50 style A3 fill:#fff,stroke:#e94560,color:#2c3e50 style LOG fill:#f8f9fa,stroke:#e0e0e0,color:#2c3e50 style SECRET fill:#f8f9fa,stroke:#e0e0e0,color:#2c3e50

Azure Container Apps Environment — architecture overview

A Container Apps Environment is the shared boundary for every container app, similar to a Kubernetes namespace. Apps in the same environment share a virtual network, logging configuration, and Dapr components.

Envoy Proxy handles ingress routing, TLS termination, and traffic splitting between revisions. KEDA (Kubernetes Event-Driven Autoscaling) controls scaling based on HTTP traffic, queue depth, cron schedule, or any metric KEDA supports. Dapr (Distributed Application Runtime) provides microservices building blocks: service invocation, state management, pub/sub, bindings, and secrets.

3. Compared with AKS, App Service, and Container Instances

Azure offers many container services. The table below helps pick the right one for the job:

Criterion Container Apps AKS App Service Container Instances
Infrastructure mgmt Serverless, none to manage You manage the cluster Managed PaaS Serverless, simple
Scaling 0 → N, KEDA-based HPA, VPA, Cluster Autoscaler 1 → 30 instances No auto-scale
Scale-to-zero ✅ Yes ❌ No (always a node) ❌ No (always an instance) N/A
Dapr integration ✅ Built-in Self-install ❌ No ❌ No
Revision management ✅ Traffic splitting Manage Deployments yourself Deployment slots ❌ No
GPU support ✅ Serverless GPU (GA 2026) ✅ GPU node pools ❌ No ✅ GPU containers
Jobs/Cron ✅ Native Jobs CronJob resource WebJobs Schedule yourself
Free tier ✅ 180K vCPU-s/month Free control plane F1 tier (limited) No free tier
Best for Microservices, APIs, event-driven Complex workloads, full control Simple web apps Short batch jobs

When to pick ACA over AKS?

If your team is under 5 people without a dedicated DevOps/Platform engineer, ACA is almost always the better choice. You save hundreds of hours a year not managing cluster upgrades, node draining, and network policies. Only reach for AKS when you truly need full control (custom operators, complex service mesh, multi-tenancy at the cluster level).

4. Core features

4.1. Ingress & Traffic Management

ACA provides built-in HTTP ingress with automatic TLS termination. You don't need to install NGINX Ingress Controller or cert-manager — flip on ingress and you have an HTTPS endpoint.

# Create a container app with ingress
az containerapp create \
  --name my-api \
  --resource-group my-rg \
  --environment my-env \
  --image myregistry.azurecr.io/my-api:v1 \
  --target-port 8080 \
  --ingress external \
  --min-replicas 0 \
  --max-replicas 10

Traffic splitting enables Blue/Green deployments and A/B testing:

# Split traffic: 80% current revision, 20% new revision
az containerapp ingress traffic set \
  --name my-api \
  --resource-group my-rg \
  --revision-weight my-api--v1=80 my-api--v2=20

4.2. Revision Management

Every time you change the container image or configuration, ACA creates a new revision. You can run multiple revisions simultaneously and split traffic between them — ideal for canary deployments.

graph LR
    LB["Load Balancer"]
    LB -->|"80%"| R1["Revision v1
3 replicas"] LB -->|"20%"| R2["Revision v2
1 replica"] R1 --> DB[(Database)] R2 --> DB style LB fill:#e94560,stroke:#fff,color:#fff style R1 fill:#fff,stroke:#e94560,color:#2c3e50 style R2 fill:#f8f9fa,stroke:#4CAF50,color:#2c3e50 style DB fill:#2c3e50,stroke:#fff,color:#fff

Traffic splitting between two revisions for a canary deployment

4.3. Secret Management

ACA lets you define secrets directly or reference them from Azure Key Vault. Secrets are injected into the container via environment variables — no credentials live inside the container image.

# Add a secret from Key Vault
az containerapp secret set \
  --name my-api \
  --resource-group my-rg \
  --secrets "db-conn=keyvaultref:https://my-vault.vault.azure.net/secrets/db-connection,identityref:/subscriptions/.../my-identity"

5. Free Tier in detail — near-zero production cost

This is ACA's most attractive feature: a free tier generous enough to run production for small and mid-sized apps.

Resource Free Tier (per subscription/month) After the free limit
vCPU 180,000 vCPU-seconds $0.000024/vCPU-second
Memory 360,000 GiB-seconds $0.000003/GiB-second
Requests 2,000,000 requests $0.40/million requests
Scale-to-zero ✅ Not billed when replicas = 0
Idle charge Discounted rate while idle Lower than active rate

What's the free tier actually enough for?

180,000 vCPU-seconds ≈ one 0.25 vCPU container running continuously for ~8.3 days, or many scale-to-zero containers running only when there's a request. For a small API receiving a few hundred requests per day, the free tier covers the whole month for $0. Health-probe requests aren't billed either.

A realistic cost estimate

Suppose you have one .NET 10 API with 0.5 vCPU / 1 GiB RAM, averaging 50,000 requests/day, processing time ~100 ms/request:

Active time/day: 50,000 × 100ms = 5,000 seconds
vCPU-seconds/day: 5,000 × 0.5 = 2,500
vCPU-seconds/month: 2,500 × 30 = 75,000  ← within free tier (180,000)

GiB-seconds/day: 5,000 × 1 = 5,000
GiB-seconds/month: 5,000 × 30 = 150,000  ← within free tier (360,000)

Requests/month: 50,000 × 30 = 1,500,000  ← within free tier (2,000,000)

→ Cost: $0/month

6. Deploying .NET 10 apps to ACA

6.1. An optimized Dockerfile for .NET 10

FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
COPY *.csproj .
RUN dotnet restore
COPY . .
RUN dotnet publish -c Release -o /app --no-restore

FROM mcr.microsoft.com/dotnet/aspnet:10.0-alpine AS runtime
WORKDIR /app
COPY --from=build /app .

ENV ASPNETCORE_URLS=http://+:8080
ENV DOTNET_EnableDiagnostics=0
EXPOSE 8080
ENTRYPOINT ["dotnet", "MyApi.dll"]

Why use an Alpine image?

The aspnet:10.0-alpine image is only ~110 MB compared to ~220 MB for the Debian image. Lighter containers → faster cold start, faster image pulls, saved bandwidth. With Native AOT, images can drop below 50 MB.

6.2. Deploying via Azure CLI

# Create the resource group and environment
az group create --name rg-myapp --location southeastasia
az containerapp env create \
  --name env-myapp \
  --resource-group rg-myapp \
  --location southeastasia

# Build and push the image to ACR
az acr create --name myappacr --resource-group rg-myapp --sku Basic
az acr build --registry myappacr --image my-api:v1 .

# Deploy the container app
az containerapp create \
  --name api-myapp \
  --resource-group rg-myapp \
  --environment env-myapp \
  --image myappacr.azurecr.io/my-api:v1 \
  --registry-server myappacr.azurecr.io \
  --target-port 8080 \
  --ingress external \
  --cpu 0.25 --memory 0.5Gi \
  --min-replicas 0 \
  --max-replicas 5 \
  --env-vars "ASPNETCORE_ENVIRONMENT=Production"

6.3. Deploying with .NET Aspire

If you're using .NET Aspire, deploying to ACA is trivial with azd (Azure Developer CLI):

# Initialize an Aspire project
dotnet new aspire-starter -n MyApp
cd MyApp

# Deploy to ACA
azd init
azd up

Aspire automatically creates the Container Apps Environment, pushes container images to ACR, configures service discovery between projects, and sets up health checks — all with two commands.

6.4. Configuring Health Probes

ACA supports the same three types of health probes as Kubernetes:

// Program.cs - Configure health checks
builder.Services.AddHealthChecks()
    .AddSqlServer(builder.Configuration.GetConnectionString("Default")!)
    .AddRedis(builder.Configuration.GetConnectionString("Redis")!);

app.MapHealthChecks("/healthz/live", new HealthCheckOptions
{
    Predicate = _ => false // Liveness: only checks the app is alive
});

app.MapHealthChecks("/healthz/ready", new HealthCheckOptions
{
    Predicate = check => check.Tags.Contains("ready") // Readiness: checks dependencies
});
# container app config
properties:
  template:
    containers:
      - name: my-api
        probes:
          - type: Liveness
            httpGet:
              path: /healthz/live
              port: 8080
            periodSeconds: 10
          - type: Readiness
            httpGet:
              path: /healthz/ready
              port: 8080
            periodSeconds: 5
          - type: Startup
            httpGet:
              path: /healthz/live
              port: 8080
            failureThreshold: 30
            periodSeconds: 2

7. Dapr Integration — Microservices without vendor lock-in

Dapr (Distributed Application Runtime) is a building-block runtime for microservices, and ACA integrates Dapr at the platform level — just flip a flag, no install or sidecar management required.

graph LR
    subgraph ACA_ENV["Container Apps Environment"]
        subgraph APP1["Order Service"]
            C1["App Code"]
            D1["Dapr Sidecar"]
        end
        subgraph APP2["Payment Service"]
            C2["App Code"]
            D2["Dapr Sidecar"]
        end
        subgraph APP3["Email Service"]
            C3["App Code"]
            D3["Dapr Sidecar"]
        end
    end
    C1 -->|"HTTP/gRPC"| D1
    D1 -->|"Service Invocation"| D2
    D2 --> C2
    D1 -->|"Pub/Sub"| D3
    D3 --> C3
    style C1 fill:#fff,stroke:#e94560,color:#2c3e50
    style C2 fill:#fff,stroke:#e94560,color:#2c3e50
    style C3 fill:#fff,stroke:#e94560,color:#2c3e50
    style D1 fill:#e94560,stroke:#fff,color:#fff
    style D2 fill:#e94560,stroke:#fff,color:#fff
    style D3 fill:#e94560,stroke:#fff,color:#fff

The Dapr sidecar pattern inside Azure Container Apps

The most useful Dapr building blocks

Building Block Description Default backend on ACA
Service Invocation Call another service by name — no IP/port needed Internal DNS
State Management Key-value store for sessions, carts, preferences Azure Cosmos DB, Azure Table Storage
Pub/Sub Publish/Subscribe messaging between services Azure Service Bus, Azure Event Hubs
Bindings Input/output bindings to external systems Azure Blob Storage, Azure Queue, SMTP
Secrets Access secrets from secret stores Azure Key Vault
// Call the Payment Service via Dapr (no URL knowledge needed)
using var client = new DaprClientBuilder().Build();

var order = new Order { Id = "ORD-001", Amount = 99.99m };
var result = await client.InvokeMethodAsync<Order, PaymentResult>(
    "payment-service",  // App ID of the target service
    "api/process",
    order
);

// Pub/Sub: publish an event
await client.PublishEventAsync("pubsub", "order-completed", order);

// State Management: save state
await client.SaveStateAsync("statestore", $"order-{order.Id}", order);

8. Autoscaling with KEDA — from zero to thousands of replicas

ACA uses KEDA (Kubernetes Event-Driven Autoscaling) to scale based on many different event sources. This is a clear edge over App Service (CPU/memory only) and Container Instances (no auto-scale).

Common scaling rules

# Scale by HTTP traffic
scale:
  minReplicas: 0
  maxReplicas: 20
  rules:
    - name: http-rule
      http:
        metadata:
          concurrentRequests: "50"

---
# Scale by Azure Service Bus queue
scale:
  minReplicas: 0
  maxReplicas: 30
  rules:
    - name: queue-rule
      custom:
        type: azure-servicebus
        metadata:
          queueName: orders
          messageCount: "5"
        auth:
          - secretRef: sb-connection
            triggerParameter: connection

---
# Scale by cron schedule
scale:
  minReplicas: 1
  maxReplicas: 10
  rules:
    - name: business-hours
      custom:
        type: cron
        metadata:
          timezone: "Asia/Ho_Chi_Minh"
          start: "0 8 * * 1-5"
          end: "0 18 * * 1-5"
          desiredReplicas: "5"

Scale-to-zero caveats

When scaling down to 0 replicas, the first request pays a cold start (typically 2-5 seconds for .NET). To mitigate, set minReplicas: 1 (you'll lose free tier for idle time) or use Native AOT to drop startup below 100 ms. Apps scaled by CPU/memory can't go to zero.

9. Jobs — Background and scheduled tasks

Besides apps (services that run continuously), ACA supports Jobs — containers that run to completion and stop. There are three trigger types:

Trigger type Description Use case
Manual Runs on demand (API call or CLI) Database migrations, one-off scripts
Schedule Runs on a cron expression Daily reports, cleanup, data sync
Event Runs when there's an event from a queue/topic Image processing, batch email, ETL
# Create a scheduled job that runs at 2:00 AM daily
az containerapp job create \
  --name job-daily-report \
  --resource-group rg-myapp \
  --environment env-myapp \
  --image myappacr.azurecr.io/report-generator:v1 \
  --trigger-type Schedule \
  --cron-expression "0 2 * * *" \
  --cpu 0.5 --memory 1Gi \
  --replica-timeout 3600 \
  --env-vars "DB_CONN=secretref:db-connection"

10. Networking & Security

10.1. Network architecture

graph TB
    INET["Internet"]
    subgraph VNET["Azure Virtual Network"]
        subgraph SUBNET["ACA Subnet"]
            ENV["Container Apps Environment"]
            APP1["Public App
(External Ingress)"] APP2["Internal Service
(Internal Ingress)"] APP3["Background Worker
(No Ingress)"] end subgraph PRIV["Private Subnet"] DB["Azure SQL"] KV["Key Vault"] end end INET -->|"HTTPS"| APP1 APP1 -->|"Internal DNS"| APP2 APP2 --> APP3 APP2 --> DB APP1 --> KV style INET fill:#e94560,stroke:#fff,color:#fff style APP1 fill:#fff,stroke:#e94560,color:#2c3e50 style APP2 fill:#fff,stroke:#2c3e50,color:#2c3e50 style APP3 fill:#f8f9fa,stroke:#e0e0e0,color:#2c3e50 style DB fill:#2c3e50,stroke:#fff,color:#fff style KV fill:#2c3e50,stroke:#fff,color:#fff

Networking: combining external/internal ingress with VNet integration

ACA supports three levels of network access:

  • External ingress: reachable from the internet over HTTPS
  • Internal ingress: only reachable within the environment (service-to-service)
  • No ingress: no inbound traffic, runs in the background only (workers, jobs)

10.2. Managed Identity

Instead of storing connection strings or API keys, use Managed Identity to authenticate with Azure services — no credentials to manage:

// Use Managed Identity to access Azure SQL
builder.Services.AddDbContext<AppDbContext>(options =>
{
    var conn = new SqlConnection(builder.Configuration.GetConnectionString("Default"));
    conn.AccessToken = new DefaultAzureCredential()
        .GetToken(new TokenRequestContext(new[] { "https://database.windows.net/.default" }))
        .Token;
    options.UseSqlServer(conn);
});

// Managed Identity for Azure Blob Storage
builder.Services.AddAzureClients(clientBuilder =>
{
    clientBuilder.AddBlobServiceClient(new Uri("https://mystorage.blob.core.windows.net"));
    clientBuilder.UseCredential(new DefaultAzureCredential());
});

10.3. Confidential Computing (Preview 2026)

ACA now supports Trusted Execution Environments (TEEs) for workloads that need hardware-level data security — data stays encrypted even while being processed in memory. A fit for fintech, healthcare, and apps handling PII.

11. Observability & Monitoring

ACA integrates natively with Azure Monitor and Log Analytics. All stdout/stderr from containers is collected automatically.

// Configure OpenTelemetry for ACA
builder.Services.AddOpenTelemetry()
    .WithTracing(tracing => tracing
        .AddAspNetCoreInstrumentation()
        .AddHttpClientInstrumentation()
        .AddSqlClientInstrumentation()
        .AddOtlpExporter())
    .WithMetrics(metrics => metrics
        .AddAspNetCoreInstrumentation()
        .AddRuntimeInstrumentation()
        .AddOtlpExporter());
// KQL query: top 10 slowest endpoints
ContainerAppConsoleLogs_CL
| where ContainerAppName_s == "my-api"
| where Log_s contains "Request finished"
| parse Log_s with * "in " Duration:real "ms"
| summarize avg(Duration), p95=percentile(Duration, 95) by Endpoint=extract("Path: ([^ ]+)", 1, Log_s)
| top 10 by p95 desc

12. What's new in 2026

January 2026
Flexible Workload Profile — combines the simplicity of Consumption with the performance of Dedicated. Pay-per-use but with scheduled maintenance, dedicated networking, and larger replicas.
March 2026
Serverless GPU GA — run AI/ML workloads on GPUs without provisioning a cluster. Auto-scaling, per-second billing, optimized cold start for inference.
March 2026
Confidential Computing Preview — hardware-based TEEs (Trusted Execution Environments) protecting data-in-use. Great for fintech and healthcare.
March 2026
Dynamic Workers — es for AI agents with automatic horizontal scaling and persistent state. Combined with Durable Object Facets to deliver an isolated SQLite per agent.
April 2026
Private Endpoints GA — reach the Container Apps environment via a private IP inside a VNet, never exposed to the internet. Workflows v2 — a rearchitected control plane for higher-concurrency background tasks.

13. Production best practices

13.1. Container image

  • Use Alpine or distroless base images to reduce the attack surface
  • Multi-stage build: separate build stage (SDK) from runtime stage (ASP.NET runtime)
  • Pin specific image versions (aspnet:10.0.1-alpine), avoid :latest
  • Use .dockerignore to exclude bin/, obj/, .git/

13.2. Configuration & secrets

  • Use environment variables instead of appsettings.json for cloud config
  • Sensitive values → Azure Key Vault references, never hardcoded
  • Managed Identity for every Azure service — don't use connection strings with passwords

13.3. Scaling & performance

  • Set minReplicas: 0 for services that don't need always-on (enjoy the free tier)
  • Use the concurrentRequests scaling rule for HTTP APIs (usually 50-100)
  • Enable Native AOT for minimal APIs to cut cold start from ~3 s to ~100 ms
  • Design stateless — don't keep state in the container, use external stores (Redis, Cosmos DB)

13.4. Reliability

  • Configure Liveness, Readiness, Startup probes fully
  • Use revision management + traffic splitting for zero-downtime deploys
  • Deploy to Southeast Asia (Singapore) for the lowest latency from Vietnam
  • Set up alerts on replica count, error rate, and response time

Production deployment checklist

✅ Health probes (liveness + readiness) configured
✅ Managed Identity for Azure services
✅ Secrets stored in Key Vault
✅ Min 2 replicas for critical services
✅ VNet integration for database access
✅ Log Analytics workspace configured
✅ Custom domain + TLS certificate
✅ Resource limits (CPU/memory) explicitly set

14. Conclusion

Azure Container Apps is the ideal choice for most .NET developers who want to run containers without facing Kubernetes's complexity. With a generous free tier (180K vCPU-seconds, 2M requests/month), scale-to-zero, built-in Dapr integration, and new 2026 features like Serverless GPU and Confidential Computing, ACA is strong enough for everything from side projects to serious production workloads.

If you're on App Service and want more control, or using AKS but finding it too complex for your team size — try ACA. You'll be surprised how simple it feels while still being production-grade.

References