System DesignMedium
Azure Functions vs App Service vs Container Apps — decision matrix
Three popular Azure compute options for HTTP and background workloads. Same code might run on any of them; the right choice depends on scaling shape, runtime control, and price.
| Azure Functions (Consumption) | App Service (Web App) | Container Apps | |
|---|---|---|---|
| Compute model | Event-driven serverless | VM-backed, long-running | Serverless containers (KEDA + Envoy) |
| Cold start | Yes (sub-second to seconds) | None (warm) | Yes, but scale-to-zero optional |
| Max execution time | 10 min (default), 60 min (premium) | Unlimited | Unlimited |
| Bring your own container? | Custom handlers via container | Yes (Linux) | Yes (first-class) |
| Scale-to-zero | Yes | No (always >= 1 instance) | Yes |
| Best for | Bursty workloads, queue consumers, webhooks | Web sites, APIs with steady traffic | Microservices with custom containers, KEDA scalers |
| Pricing | Pay per execution + GB-s | Per hour of VM | Per vCPU-second + memory-second |
When to pick Functions
- Workload is bursty — Sundays do 100 RPS, Tuesdays do 0.
- Event source (Service Bus, Storage Queue, Event Grid, Cosmos changefeed) — bindings are first-class.
- You do not want to think about scale tuning.
When to pick App Service
- Predictable, sustained traffic — a public marketing site, internal LOB app.
- You need built-in SSL/custom domains, deployment slots, easy "swap" rollouts.
- Workload includes long-lived processes (SignalR with many open connections) that do not match the Functions model.
When to pick Container Apps
- You already ship as a container.
- You want scale-to-zero plus KEDA-driven scaling on custom signals (RabbitMQ queue length, Kafka lag, anything KEDA supports).
- You need revisions (multiple versions live with traffic split — "blue/green out of the box").
- You want Dapr for service-to-service calls and pub/sub abstraction.
Sample matrix decisions
- Webhook receiver for Stripe — Functions. Bursty, short, exactly fits the event-driven model.
- Customer-facing API at 100 RPS sustained — App Service P1V3 with 2-3 instances. Functions cold start hurts at scale.
- Microservice that consumes from Service Bus and scales 1-50 based on queue depth — Container Apps. Scale-to-zero off-hours saves real money.
- Cron job nightly batch — Functions Timer trigger if under 10 min, otherwise Container Apps Job or App Service WebJob.
Common cost gotchas
- Functions Premium plan is no longer pay-per-execution — it is per VM-hour, with cold-start guarantees. Pick it only when you actually need the no-cold-start guarantee.
- App Service "Always On" prevents cold start but charges 24/7 — for low traffic, Functions Consumption is much cheaper.
- Container Apps
minReplicas = 1disables scale-to-zero. Set to0explicitly for cost saving.
Migration paths
- Functions can run as a container today — migrating to Container Apps is mostly a YAML change.
- App Service can run containers — the migration to Container Apps is incremental (start with a single service).
- Container Apps "Jobs" cover one-off and scheduled batch tasks; you do not need to keep Functions around just for cron.