Overview
Azure offers compute at multiple points on the same control-versus-convenience spectrum every cloud provider does: Virtual Machines give full OS-level control, App Service gives a fully managed platform for standard web workloads, and AKS gives managed Kubernetes for teams that need container orchestration without owning the control plane themselves. Picking between them is a workload-fit decision, not a maturity ladder, a standard REST API is usually a worse fit for a hand-managed VM than for App Service, while a workload needing custom OS-level tuning or unsupported dependencies genuinely needs a VM regardless of how "modern" the alternatives look.
Quick Reference
| Service | Model | You manage | Best fit |
|---|---|---|---|
| Virtual Machine | IaaS | OS, runtime, scaling, patching | Custom OS/runtime needs, full control |
| VM Scale Set | IaaS, fleet-managed | Same as VM, but as a scalable group | Identical VMs that need to scale with load |
| App Service | PaaS | App code and config only | Standard web apps/APIs in supported runtimes |
| AKS | Managed Kubernetes | Worker nodes, workloads | Container orchestration without self-hosting the control plane |
Syntax
# App Service - deploy a web app without managing any VM
az webapp up --name my-api --runtime "NODE:20-lts" --sku B1resource aks 'Microsoft.ContainerService/managedClusters@2024-01-01' = {
name: 'aks-app'
location: resourceGroup().location
properties: {
dnsPrefix: 'aksapp'
agentPoolProfiles: [
{ name: 'default', count: 3, vmSize: 'Standard_DS2_v2', mode: 'System' }
]
}
}Examples
# Deployment slots let App Service stage a new version and swap
# it into production with near-zero downtime, without a VM Scale
# Set's more involved rolling-update configuration.
az webapp deployment slot create --name my-api --resource-group rg-app --slot staging
az webapp deployment slot swap --name my-api --resource-group rg-app --slot stagingStart with the most managed option that fits
Default to App Service or AKS for standard workloads, and drop to a plain VM only when you hit a specific, genuine requirement it can't satisfy, not as a default starting point.
Visual Diagram
Common Mistakes
- Defaulting to a Virtual Machine out of familiarity for a standard web workload that App Service would handle with far less operational burden.
- Running Kubernetes on self-managed VMs without a specific reason, taking on control-plane operations AKS would otherwise handle.
- Not using deployment slots on App Service, and instead deploying straight to production with real downtime during releases.
- Undersizing a VM Scale Set's autoscale rules based on a single metric (CPU) when the actual bottleneck is something else (queue depth, memory), leaving the workload under-provisioned despite autoscaling being "on."
Performance
- App Service's managed scaling reacts to configured rules and has some warm-up latency for new instances, very spiky, unpredictable traffic may need pre-warming or a lower scale-out threshold to stay ahead of demand.
- AKS worker node performance is standard VM performance, choosing the right VM size for the workload matters exactly as much as it would for a plain VM.
- VM Scale Sets scale linearly with configured limits; the ceiling is a deliberate configuration choice, not an automatic one, so it needs to be set with real peak load in mind.
Best Practices
- Choose the most managed compute option that actually fits the workload's constraints, and only drop to more control (VM) when there's a specific, named reason.
- Use deployment slots (App Service) or rolling updates (AKS) for zero-downtime releases rather than deploying directly to the serving instance.
- Size AKS node pools and VM Scale Sets based on real observed load, and autoscale on the metric that actually reflects the bottleneck, not just CPU by default.
- Revisit the compute choice as a workload matures, constraints that justified a VM early on may no longer apply once the workload stabilizes.