Skip to content

Azure Container Apps & AKS

Key Points

  • Azure Container Apps (ACA): managed serverless containers built on K8s + KEDA + Dapr + Envoy. Scale-to-zero; pay per consumption; no K8s knowledge required.
  • AKS (Azure Kubernetes Service): managed K8s. Full control; full responsibility. Choose for complex multi-team, advanced networking, custom controllers.
  • ACI (Azure Container Instances): simplest single-container; for batch / one-off tasks.
  • For 2026, ACA is the default for most container workloads. AKS for "we need K8s" reasons. Aspire deploys to ACA out of the box.

Concepts (deep dive)

What ACA and AKS are (and why both exist)

Both run your container in Azure. The difference is who operates the Kubernetes underneath and how much of Kubernetes you can see.

  • ACA (Azure Container Apps) is a serverless container platform. Microsoft runs the Kubernetes cluster, the KEDA installation, the Dapr runtime, and the Envoy ingress; you see containers and revisions, not pods and deployments. You don't write YAML for kind: Deployment; you write "give me this image with these scale rules" and ACA materializes the rest. It's the right answer when you want container hosting without inheriting K8s operations.
  • AKS (Azure Kubernetes Service) is a managed Kubernetes control plane. Microsoft runs the API server, etcd, and the scheduler; you run everything else — node pools, ingress controllers, secret operators, monitoring add-ons, upgrade choreography. You get the full Kubernetes API surface, the entire CNCF ecosystem, and the full operational responsibility that comes with it.
              you write & operate                        Microsoft operates
              ──────────────────                         ────────────────────
   ACA:       container + scale rules ──────────────►   K8s control plane,
              (revisions, ingress URL,                   nodes, KEDA, Dapr,
               Dapr opt-in)                              Envoy, upgrades, OS
              ──────────────────────────────────────────────────────────────

   AKS:       node pools, ingress, secret store,          K8s control plane,
              monitoring, mesh, operators,                 API server, etcd
              upgrade choreography, every YAML ──────►    (and that's about it)
              ──────────────────────────────────────────────────────────────

   ACI:       single container + restart policy ────►    everything else
              (no autoscale, no rolling deploy)

Why ACA exists at all: for the majority of .NET microservice workloads, the "operate your own K8s" cost was strictly bigger than the value of K8s flexibility. ACA is what you get when you keep the parts almost every team needed (autoscale, ingress, revisions, secrets, identity) and remove the parts almost no team needed (CRDs, custom controllers, node-level access). For a 2026 greenfield .NET app, ACA is the default; AKS is the "we have a reason" choice.

Azure Container Apps

The unit of deployment is the container app — your image, ports, environment, scale rules. The minimal create looks like a slightly opinionated docker run plus scale settings. Notice --min-replicas 0: this is scale-to-zero, the feature that fundamentally changes the cost shape compared to a fixed-size App Service plan.

az containerapp create \
  --name myapp \
  --resource-group rg \
  --environment myenv \
  --image myacr.azurecr.io/myapp:1.0 \
  --target-port 8080 \
  --ingress external \
  --min-replicas 0 \
  --max-replicas 10 \
  --scale-rule-name http \
  --scale-rule-http-concurrency 30

Built on: - AKS (managed) — you don't see the K8s. - KEDA — event-driven scale (queue depth, custom). - Dapr — sidecars for state, pub/sub, secrets. - Envoy — ingress.

Scale-to-zero

minReplicas: 0 means: when there is no demand (no HTTP traffic, no queue messages, no events matching scale rules), ACA terminates every replica and bills nothing. When work arrives, ACA spins a replica up — that's the cold start. For internal APIs that see hours of idle, scale-to-zero is the difference between $30/mo and $0/mo; for a customer-facing endpoint where the first request after idle would feel a multi-second hesitation, set minReplicas: 1 and pay for the warm replica.

scale:
  minReplicas: 0
  maxReplicas: 30
  rules:
  - name: http
    http:
      metadata: { concurrentRequests: "30" }
  - name: queue
    custom:
      type: azure-servicebus
      metadata:
        queueName: orders
        messageCount: "5"

No traffic → 0 replicas → $0. First request: cold start (~few seconds). Acceptable for many workloads.

KEDA scale rules

KEDA (built into ACA) decides when to scale by reading metrics from outside the container — queue depth, lag, schedule, custom Prometheus metric. For a queue worker, the right signal is "messages waiting," not "CPU"; KEDA fetches that signal and decides the replica count. Each rule is independent; the highest count wins. Pick a rule that actually correlates with the work the app is doing — CPU is rarely it for I/O-bound .NET workloads.

Source Use
HTTP RPS / concurrency
Service Bus Queue depth
Event Hub Lag
Kafka Consumer lag
Cron Scheduled
Custom Prometheus metric

Dapr integration

Dapr (Distributed Application Runtime) is a sidecar that the app talks to over localhost for cross-cutting concerns — state store, pub/sub, secrets, service invocation, distributed tracing. The point is decoupling: your code calls "publish to topic X"; the Dapr sidecar handles the actual broker (Service Bus, Kafka, Redis, …) chosen by configuration. Swap the backing service without touching the application.

ACA has Dapr integrated; opt in per app with dapr.enabled: true. On AKS you install Dapr yourself.

dapr:
  enabled: true
  appPort: 8080
  appProtocol: http
// In your app, call Dapr sidecar:
var client = new DaprClientBuilder().Build();
await client.PublishEventAsync("pubsub", "orders.placed", order);
var state = await client.GetStateAsync<Cart>("statestore", "cart-123");

Dapr provides building blocks (state, pub/sub, secrets, service invocation) without code coupling to Azure.

Revisions

A revision is an immutable snapshot of an app's config + image. Every update creates a new revision; the old revision keeps running. Traffic routing is a separate decision: send 100% to the new one, split for a canary, or keep two revisions live for blue-green. Rollback is a traffic-routing change, not a redeploy.

revision-1 (100%) → revision-2 (deploy) → split 80/20 → 100% revision-2

Each deploy creates a new revision. Traffic split for canary deploys. Rollback = traffic to old revision.

Networking

  • External: public ingress with FQDN.
  • Internal: only within VNet.
  • VNet integration: deploy into customer VNet for private access to other Azure resources.

Container Apps Environment

The environment is the trust boundary in ACA — the shared VNet, the shared Log Analytics workspace, the shared Dapr namespace. Apps inside the same environment can reach each other on private DNS and share telemetry; apps in different environments are isolated. Typical layout: one environment per environment-with-a-lowercase-e (dev, staging, prod), all apps in that environment colocated.

The boundary. Apps in same environment share: - VNet - Log Analytics workspace - Dapr namespace

AKS

Provisioning AKS is one CLI call; operating AKS is an ongoing job. The "we manage the control plane, you manage everything else" boundary means every team that picks AKS needs to make decisions about ingress (NGINX vs AGIC vs Istio gateway), secrets (Key Vault CSI vs External Secrets vs Sealed Secrets), monitoring (Container Insights vs Prometheus stack), workload identity, node-pool topology, and upgrade choreography. None of those are hard individually; together they're a platform team's full job. Worth it when you genuinely need K8s; expensive overhead otherwise.

az aks create -g rg -n mycluster --node-count 3 --enable-managed-identity
az aks get-credentials -g rg -n mycluster
kubectl apply -f manifest.yaml

Full K8s. You manage: - Workload identities (Azure AD Workload Identity). - Ingress controller. - Secret management (Key Vault CSI). - Monitoring (Container Insights). - Upgrades.

When ACA vs AKS

Scenario Pick
Greenfield .NET microservices ACA
Existing K8s expertise + workloads AKS
Multi-tenant K8s platform AKS
Service mesh (Istio/Linkerd) needed AKS
Custom controllers / operators AKS
GPU workloads AKS
Scale-to-zero ACA
Dapr-based architecture ACA

Pricing

  • ACA: per-vCPU-second + per-GiB-second + per-request. Free tier: 180k vCPU-s, 360k GiB-s.
  • AKS: cluster mgmt is free (control plane); pay for VMs and disks.

For burst-y workloads, ACA cheaper. For sustained, AKS often cheaper.

.NET on ACA

Aspire (Aspire — Cloud-Native) deploys to ACA via azd up. AppHost topology → ACA resources.

azd init                     # creates azure.yaml from AppHost
azd up                       # provisions + deploys

.NET on AKS

# Deployment with workload identity
spec:
  template:
    metadata:
      annotations:
        azure.workload.identity/use: "true"
    spec:
      serviceAccountName: myapp-sa
      containers:
      - name: app
        image: myacr.azurecr.io/myapp:1.0
        env:
        - name: ASPNETCORE_ENVIRONMENT
          value: Production

ACI for batch

az container create -g rg -n batch-job --image myimage --restart-policy Never

Single container; runs to completion. Cheap for one-offs, integration tests in CI, etc.

Service mesh on AKS

Open Service Mesh, Linkerd, Istio. mTLS between services; retries; canary; observability.

ACA bundles Envoy + Dapr for many of these features without explicit mesh.

Storage

  • ACA: ephemeral (and Azure Files mount for persistent).
  • AKS: full PV/PVC ecosystem.

Logging

  • ACA: Log Analytics workspace (auto).
  • AKS: Container Insights (Azure Monitor) or self-hosted (Loki).

Health probes

ACA passes /health to your container. Configure:

probes:
- type: liveness
  httpGet: { path: /healthz/live, port: 8080 }
- type: readiness
  httpGet: { path: /healthz/ready, port: 8080 }
- type: startup
  httpGet: { path: /healthz/startup, port: 8080 }

Code: correct vs wrong

❌ Wrong: AKS for a single .NET app

az aks create ...   # operational overhead

✅ Correct: ACA

az containerapp create ...

❌ Wrong: managed identity not configured on ACA

az containerapp create --image ...   # no identity

✅ Correct: assign identity

az containerapp identity assign --system-assigned
az role assignment create --assignee $principalId --role "Key Vault Secrets User" --scope $kvId

Design patterns for this topic

Pattern 1 — "ACA default; AKS when needed"

  • Intent: complexity-appropriate platform.

Pattern 2 — "Dapr for cloud-agnostic"

  • Intent: decoupled from Azure SDKs.

Pattern 3 — "KEDA scale-to-zero"

  • Intent: cost-efficient bursty workloads.

Pattern 4 — "Revisions for canary"

  • Intent: safe deploys.

Pattern 5 — "Workload identity (no secrets)"

  • Intent: zero-secret cloud auth.

Pros & cons / trade-offs

Platform Pros Cons
ACA Managed; KEDA; Dapr Less flexible than K8s
AKS Full control Operational
ACI Simplest Single container
App Service Easiest .NET Not container-native

When to use / when to avoid

  • Use ACA for greenfield .NET microservices.
  • Use AKS when you need full K8s.
  • Use ACI for batch jobs.
  • Avoid AKS for single-app simplicity.

Interview Q&A

Q1. ACA vs AKS? ACA: managed serverless containers; built on K8s+KEDA+Dapr+Envoy. AKS: managed K8s; full control.

Q2. Why scale-to-zero? Idle apps cost $0. First request cold-starts.

Q3. KEDA? Event-driven autoscaler. Scale on queue depth, custom metrics.

Q4. Dapr building blocks? State, pub/sub, secrets, service invocation, observability — language-agnostic abstractions via sidecar.

Q5. ACA revisions? Each deploy = new revision. Traffic split for canary; rollback by re-routing.

Q6. Workload identity? Pod's service account ↔ Azure AD app. No secrets stored.

Q7. When AKS? Service mesh, custom operators, multi-team K8s platform, advanced networking.

Q8. ACA networking modes? External (public), internal (VNet only).

Q9. ACA pricing? vCPU-seconds + GiB-seconds + requests. Free tier covers small apps.

Q10. .NET Aspire deploy? azd up deploys AppHost topology to ACA.

Q11. Service mesh in ACA? Bundled Envoy + Dapr cover many features. Full mesh — use AKS.

Q12. ACI use case? Batch jobs, CI integration tests, one-off containers.


Gotchas / common mistakes

  • ⚠️ AKS for simple apps — operational cost.
  • ⚠️ No workload identity — secret rot.
  • ⚠️ Public ingress when internal needed — security.
  • ⚠️ No probes — flapping.
  • ⚠️ Wrong KEDA rule — cost spike or under-scale.

Further reading