Azure Compute Options: App Service, Azure Functions, and AKS
A practical guide to Azure's three main compute hosting models for .NET applications — App Service for web apps and APIs, Azure Functions for serverless event-driven code, and Azure Kubernetes Service (AKS) for container orchestration — covering how each works, when to choose which, and how to deploy to them.
Table of Contents
- Introduction
- App Service
- Azure Functions
- Azure Kubernetes Service (AKS)
- Scaling Models Compared
- Networking and Security
- CI/CD and Deployment
- Cost Models
- Choosing Between Them
- Combining All Three
- Quick Reference Table
- Conclusion
Introduction
Azure offers a spectrum of compute options, from fully managed and opinionated (App Service) to fully serverless and event-driven (Functions) to fully flexible but more operationally demanding (AKS). None of them is universally "better" — each trades control for convenience differently, and most real-world Azure architectures use more than one at once.
Less operational control, more managed ◄──────────────────► More control, more responsibility
Azure Functions App Service AKS
This guide walks through each option, then compares them directly so you can match the right tool to the right workload.
1. App Service
Azure App Service is a fully managed platform for hosting web apps, REST APIs, and mobile backends. You deploy your code (or a container), and Azure handles the underlying VMs, OS patching, load balancing, and most infrastructure concerns.
What you get out of the box
- Managed OS and runtime patching — you never SSH into a VM to apply updates.
- Built-in load balancing across multiple instances.
- Deployment slots for zero-downtime releases (see below).
- Auto-scaling based on CPU, memory, or schedule.
- Native support for .NET, Node.js, Java, Python, PHP, and custom containers.
- Integrated custom domains, free-managed SSL certificates, and easy VNet integration.
Deploying an ASP.NET Core app
az webapp up --name my-api --resource-group my-rg --runtime "DOTNETCORE:9.0"
Or via a Dockerfile if you'd rather bring your own container:
az webapp create --name my-api --resource-group my-rg \
--plan my-app-service-plan --deployment-container-image-name myregistry.azurecr.io/my-api:latest
App Service Plans
Every App Service app runs within an App Service Plan — the underlying compute (VM size and instance count) that one or more apps can share. Picking a plan tier controls both cost and capability:
| Tier | Typical use |
|---|---|
| Free/Shared | Prototypes, learning, no SLA |
| Basic | Dev/test workloads, no auto-scale |
| Standard | Production apps, auto-scale, deployment slots |
| Premium | Higher-scale production, more slots, VNet integration |
| Isolated | Dedicated network (App Service Environment), high compliance needs |
Deployment slots
Slots let you deploy a new version to a separate, fully live environment (e.g., my-api-staging.azurewebsites.net) and then swap it into production with no downtime:
az webapp deployment slot create --name my-api --resource-group my-rg --slot staging
az webapp deployment source config-zip --name my-api --resource-group my-rg --slot staging --src release.zip
az webapp deployment slot swap --name my-api --resource-group my-rg --slot staging --target-slot production
This is one of App Service's most valuable built-in features: you can smoke-test the new version against production-like traffic and settings before it ever serves real users, and roll back instantly by swapping again if something's wrong.
Best fit
Traditional web applications, REST/GraphQL APIs, and backend services that run continuously and benefit from a fully managed platform without needing container orchestration complexity.
2. Azure Functions
Azure Functions is Azure's serverless compute platform: you write small, focused units of code ("functions") that run in response to triggers — an HTTP request, a queue message, a timer, a blob upload — and Azure handles provisioning, scaling, and (depending on the hosting plan) billing you only for actual execution time.
Triggers and bindings
A function is defined by its trigger (what starts it) and optionally input/output bindings (declarative ways to read/write data without hand-writing SDK calls):
public class OrderFunctions
{
[Function("ProcessOrder")]
public async Task Run(
[QueueTrigger("orders-queue")] string orderJson,
[CosmosDBOutput("Orders", "OrdersContainer", Connection = "CosmosDbConnection")] IAsyncCollector<Order> output)
{
var order = JsonSerializer.Deserialize<Order>(orderJson);
await output.AddAsync(order);
}
}
public class HttpFunctions
{
[Function("GetProduct")]
public HttpResponseData GetProduct(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "products/{id}")] HttpRequestData req,
int id)
{
var product = _repository.GetById(id);
var response = req.CreateResponse(product is not null ? HttpStatusCode.OK : HttpStatusCode.NotFound);
if (product is not null) response.WriteAsJsonAsync(product);
return response;
}
}
Common trigger types: HTTP, Timer (cron-style schedules), Queue (Storage Queue or Service Bus), Blob (file uploads), Event Grid/Event Hub (event streaming), and Cosmos DB (change feed).
Timer-triggered scheduled jobs
[Function("NightlyCleanup")]
public void Run([TimerTrigger("0 0 2 * * *")] TimerInfo timer)
{
// runs daily at 2:00 AM
}
This is a common, lightweight alternative to a full background-worker process for a job that runs briefly and infrequently — no long-running host to manage at all.
Hosting plans
| Plan | Scaling | Cold start | Billing |
|---|---|---|---|
| Consumption | Automatic, scales to zero | Yes (can be noticeable) | Pay only per execution + resource consumption |
| Premium | Automatic, pre-warmed instances | Minimal/none | Pay for pre-warmed capacity + usage |
| Dedicated (App Service Plan) | Manual/autoscale like App Service | None (always running) | Pay for the underlying VM plan regardless of usage |
| Container Apps / AKS-hosted (KEDA) | Event-driven scaling via KEDA | Depends on config | Pay for underlying compute |
The Consumption plan is the purest "serverless" experience — you pay per execution and per GB-second of memory used, and Azure scales instance count (including down to zero when idle) automatically. The tradeoff is cold start: the first request after a period of inactivity pays the cost of spinning up a fresh instance, which can add a noticeable delay for latency-sensitive workloads.
Durable Functions
For workflows that need to coordinate multiple steps, wait for external events, or run for a long time (well beyond a single function's execution limit), Durable Functions provides an orchestration framework on top of regular Functions:
[Function("OrderOrchestrator")]
public static async Task<string> RunOrchestrator([OrchestrationTrigger] TaskOrchestrationContext context)
{
var order = context.GetInput<Order>();
await context.CallActivityAsync("ValidateOrder", order);
await context.CallActivityAsync("ChargePayment", order);
await context.CallActivityAsync("ShipOrder", order);
return "Completed";
}
This handles the hard parts of long-running, stateful workflows (checkpointing progress, surviving process restarts mid-workflow, fan-out/fan-in patterns) without you building that machinery yourself.
Best fit
Event-driven processing (queue/blob/event triggers), scheduled jobs that run briefly, lightweight APIs with spiky or unpredictable traffic, and workloads where "pay only when it runs" materially matters — as opposed to something that needs to be always-warm and continuously running.
3. Azure Kubernetes Service (AKS)
AKS is Azure's managed Kubernetes offering — Azure runs and patches the Kubernetes control plane for you, while you manage (and pay for) the worker node pools that actually run your containers.
What "managed" means here
- Azure operates the Kubernetes API server and control plane — no manual
etcd/control-plane patching. - You define and manage node pools (the VMs that run your workloads), including autoscaling them.
- You still write and manage standard Kubernetes manifests (Deployments, Services, Ingress, etc.) — AKS doesn't hide Kubernetes from you, it just removes the control-plane operational burden.
A basic deployment
az aks create --resource-group my-rg --name my-cluster --node-count 3 --generate-ssh-keys
az aks get-credentials --resource-group my-rg --name my-cluster
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: product-api
spec:
replicas: 3
selector:
matchLabels: { app: product-api }
template:
metadata:
labels: { app: product-api }
spec:
containers:
- name: product-api
image: myregistry.azurecr.io/product-api:latest
ports:
- containerPort: 8080
resources:
requests: { cpu: "250m", memory: "256Mi" }
limits: { cpu: "500m", memory: "512Mi" }
---
apiVersion: v1
kind: Service
metadata:
name: product-api-service
spec:
type: LoadBalancer
selector: { app: product-api }
ports:
- port: 80
targetPort: 8080
kubectl apply -f deployment.yaml
Autoscaling at two levels
AKS supports scaling both the application (pods) and the underlying infrastructure (nodes):
kubectl autoscale deployment product-api --cpu-percent=70 --min=3 --max=10 # Horizontal Pod Autoscaler
az aks nodepool update --resource-group my-rg --cluster-name my-cluster --name nodepool1 --enable-cluster-autoscaler --min-count 2 --max-count 10 # Cluster Autoscaler
The Horizontal Pod Autoscaler (HPA) scales the number of running pod replicas based on CPU/memory or custom metrics; the Cluster Autoscaler scales the number of underlying VM nodes when there isn't enough room to schedule the pods the HPA wants to run — together they let a cluster grow and shrink automatically at both layers.
Why teams choose AKS
- Multiple services, one platform — dozens or hundreds of microservices can share a cluster's infrastructure, with Kubernetes handling scheduling, service discovery, and networking between them.
- Fine-grained control — resource limits, custom scheduling rules, sidecar patterns (like a service mesh), and complex networking policies are all available if you need them.
- Portability — a workload packaged as standard Kubernetes manifests isn't Azure-specific; the same manifests largely work on any conformant Kubernetes cluster (EKS, GKE, on-prem).
- Ecosystem — the vast Kubernetes/CNCF ecosystem (Helm charts, Prometheus, Istio, ArgoCD, KEDA) is available directly.
The real tradeoff
AKS gives you the most control and flexibility of the three options, but also the most operational surface area: you're responsible for node pool sizing and patching cadence, namespace and RBAC design, ingress configuration, monitoring the cluster itself (not just your apps), and generally understanding Kubernetes concepts that App Service and Functions abstract away entirely.
Best fit
Organizations already running (or committed to) Kubernetes, complex microservice architectures needing fine-grained orchestration control, workloads needing portability across clouds, or teams with existing Kubernetes expertise who want that control without managing the control plane themselves.
4. Scaling Models Compared
| App Service | Azure Functions (Consumption) | AKS | |
|---|---|---|---|
| Scale unit | App instance | Individual function execution | Pod (and, underneath, node) |
| Scale to zero | No (lowest tier still runs) | Yes | No (unless using KEDA-driven scale-to-zero patterns) |
| Cold start | No (always-on instances) | Yes, on Consumption plan | No, but pod scheduling has its own startup latency |
| Scaling trigger | CPU/memory/schedule rules | Automatic, per-trigger-event | HPA (app) + Cluster Autoscaler (infrastructure) |
| Granularity | Whole-app instances | Per-function, per-event | Per-pod, highly granular |
5. Networking and Security
App Service
- VNet integration lets an App Service app reach resources inside a private virtual network.
- Private Endpoints can make the app itself only reachable from within a VNet.
- Managed Identity lets the app authenticate to other Azure resources (Key Vault, Storage, SQL) without storing credentials in config.
Azure Functions
- Same VNet integration and Managed Identity support as App Service on Premium/Dedicated plans (Consumption plan has more limited networking control).
- Function-level authorization keys (
AuthorizationLevel.Function) provide simple built-in access control for HTTP triggers, though production APIs usually front Functions with Azure API Management or proper OAuth/JWT validation for anything beyond internal use.
AKS
- Network Policies control pod-to-pod traffic within the cluster.
- Azure CNI or kubenet control how pod networking integrates with the Azure VNet.
- Azure AD (Entra ID) integration for RBAC over the Kubernetes API itself, layered on top of standard Kubernetes RBAC.
- Secrets typically flow through Azure Key Vault via the Secrets Store CSI Driver, rather than plain Kubernetes Secrets (which are only base64-encoded, not encrypted, by default without extra configuration).
Shared building blocks
All three commonly sit behind Azure Front Door or Application Gateway for global load balancing, WAF (Web Application Firewall) protection, and TLS termination — the choice of App Service/Functions/AKS is about compute hosting, not about how traffic reaches your edge.
6. CI/CD and Deployment
App Service
# GitHub Actions
- uses: azure/webapps-deploy@v3
with:
app-name: my-api
package: ./publish
Deployment slots (Section 1) make this safe by default — deploy to staging, verify, then swap.
Azure Functions
- uses: azure/functions-action@v1
with:
app-name: my-function-app
package: ./publish
Functions support the same slot-swapping pattern as App Service on Premium/Dedicated plans.
AKS
- run: |
az acr build --registry myregistry --image product-api:${{ github.sha }} .
kubectl set image deployment/product-api product-api=myregistry.azurecr.io/product-api:${{ github.sha }}
Most teams layer a GitOps tool (ArgoCD, Flux) or a progressive-delivery controller (Flagger) on top of raw kubectl commands for anything beyond a simple demo — AKS deployment pipelines tend to be the most involved of the three to set up well, in exchange for the most control over the rollout process (canary releases, blue/green, automated rollback on failed health checks).
7. Cost Models
| App Service | Azure Functions (Consumption) | AKS | |
|---|---|---|---|
| Billing basis | Per-instance-hour of the App Service Plan, regardless of traffic | Per-execution + memory×time (GB-seconds); free monthly grant | Per-VM-hour for node pools, regardless of pod utilization; AKS control plane itself is free on the Standard tier |
| Idle cost | You pay for provisioned instances even at zero traffic | Near-zero at zero traffic (Consumption plan) | You pay for provisioned nodes even if pods are idle |
| Cost predictability | High — fixed plan cost | Lower for spiky workloads, harder to predict at high, constant volume | Depends heavily on node pool sizing and bin-packing efficiency |
A rough rule of thumb: Functions on Consumption is cheapest for spiky, infrequent, or unpredictable workloads; App Service is more cost-predictable for steady, continuous traffic; AKS can be the most cost-efficient at scale (many services efficiently packed onto shared nodes) but requires active capacity planning to avoid paying for idle, oversized nodes.
8. Choosing Between Them
Is this event-driven, spiky, or scheduled work
(queue processing, file triggers, cron jobs, lightweight APIs)?
│
├── Yes → Azure Functions
│
└── No — it's a continuously running web app/API
│
├── Do you need Kubernetes-level orchestration control,
│ run many interdependent microservices, or need
│ portability across clouds?
│ │
│ ├── Yes → AKS
│ │
│ └── No → App Service
Concrete scenarios
- A REST API for a mobile app backend, moderate steady traffic → App Service. Managed, simple, deployment slots for safe releases.
- Processing images uploaded to Blob Storage, resizing them, a few times a minute → Azure Functions (Blob trigger, Consumption plan).
- A platform with 40 microservices, service mesh requirements, and a dedicated platform team → AKS.
- A nightly batch job that runs for 10 minutes → Azure Functions (Timer trigger) — no reason to run a continuously-billed App Service instance just to fire once a day.
- An internal admin dashboard with predictable, low, constant traffic → App Service on a Basic/Standard plan.
9. Combining All Three
Real Azure architectures rarely pick just one. A common pattern:
- App Service hosts the main customer-facing web application and its REST API.
- Azure Functions handles asynchronous, event-driven side work triggered by that API — sending confirmation emails off a queue, processing uploaded documents, running nightly reports.
- AKS runs a set of internal, more complex microservices (perhaps including gRPC services communicating with each other) that need finer orchestration control, alongside supporting infrastructure like a message broker or a service mesh.
None of these need to know about the others' hosting model — a Function can enqueue a message that an AKS-hosted worker consumes, and an App Service app can call an AKS-hosted internal API over a private VNet connection — the compute layer is an implementation detail, not something that needs to be uniform across an entire system.
Quick Reference Table
| Concept | App Service | Azure Functions | AKS |
|---|---|---|---|
| Model | PaaS — managed web hosting | Serverless — event-driven functions | Managed container orchestration |
| Unit of deployment | Web app (code or container) | Individual functions | Pods via Kubernetes manifests |
| Scale to zero | No | Yes (Consumption plan) | No (without extra tooling) |
| Best for | Continuously running web apps/APIs | Event-driven, spiky, scheduled workloads | Complex microservices, orchestration control |
| Ops overhead | Low | Lowest | Highest |
| Zero-downtime deploys | Deployment slots | Deployment slots (Premium/Dedicated) | Rolling updates / canary via tooling |
| Networking control | VNet integration, Private Endpoints | VNet integration (Premium/Dedicated) | Full Kubernetes networking (CNI, policies) |
Conclusion
App Service, Azure Functions, and AKS aren't competing answers to the same question — they're answers to three different questions. App Service asks "how do I run a web app/API without managing infrastructure?" Functions asks "how do I run small units of code only when something specific happens, paying only for that?" AKS asks "how do I orchestrate many containerized services with full control over networking, scheduling, and scaling?"
Most production systems end up using more than one, and the right starting point is usually the option with the least operational overhead that still satisfies the workload's actual requirements — reach for AKS when you genuinely need Kubernetes-level control, not by default, and let a workload's shape (continuous vs. event-driven, simple vs. many interdependent services) point you to the right compute model rather than picking one and forcing every workload to fit it.
Found this useful? Feel free to star the repo, open an issue with corrections, or share which Azure compute model ended up being the wrong first choice for your workload.
Top comments (0)