Choosing a Compute Service

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

See the section bibliography for the reference material consulted while preparing these pages.

Commands below target Azure CLI 2.90.0 (checked 2026-09-26); re-verify flags with az vm create --help / az container create --help before relying on them, since preview flags in particular move fast.

Azure has more ways to run code than it first appears, and picking the wrong one early tends to mean a rewrite later rather than a config change. This page walks through Microsoft’s own compute decision tree, then covers the two options that don’t get their own page in this section hands-on — Virtual Machines/Scale Sets and Azure Container Instances — plus one paragraph on Azure Virtual Desktop, before ending in a single comparison table against the container and PaaS options detailed on the sibling pages linked throughout.

The official decision tree

Microsoft’s Azure Architecture Center publishes a compute decision tree that starts from the same two questions every one of Bookshelf’s services answers before choosing a target: is this a migration of something that already runs somewhere else, or a new build; and, for a new build, how much control over the underlying OS and orchestration does it actually need. The flowchart below mirrors that tree’s shape, narrowed to the options this documentation section covers:

flowchart TD Start["Need to run code in Azure"] FullOS{"Need full OS control\nor unsupported/legacy stack?"} GPUDesktop{"Need GPU workstation\nor interactive Windows desktop?"} Containerized{"Workload is containerized?"} K8s{"Need Kubernetes primitives\n(CRDs, operators, service mesh, Dapr)?"} EventDriven{"Event-driven, short-lived,\nscale-to-zero triggers?"} Interruptible{"Short-lived or interruptible\nsingle/few container runs?"} HttpFirst{"HTTP-first web workload,\nfastest path to production?"} Start --> FullOS FullOS -- Yes --> GPUDesktop GPUDesktop -- "Interactive desktop" --> AVD["Azure Virtual Desktop"] GPUDesktop -- "No, just VMs/GPU/scale" --> VM["Virtual Machines / Scale Sets"] FullOS -- No --> Containerized Containerized -- No --> Functions["Azure Functions\n(xref: app-service-and-functions.adoc)"] Containerized -- Yes --> K8s K8s -- Yes --> AKS["Azure Kubernetes Service\n(xref: aks-clusters.adoc, aks-microservices.adoc)"] K8s -- No --> EventDriven EventDriven -- Yes --> Functions EventDriven -- No --> Interruptible Interruptible -- Yes --> ACI["Azure Container Instances"] Interruptible -- No --> HttpFirst HttpFirst -- Yes --> ACAExpress["Container Apps Express\n(xref: container-apps.adoc)"] HttpFirst -- "No, needs Dapr/jobs/VNet/multi-revision" --> ContainerApps["Container Apps\n(xref: container-apps.adoc,\ncontainer-apps-scaling-jobs-and-dapr.adoc)"] HttpFirst -- "No, prefers classic PaaS model" --> AppService["App Service\n(xref: app-service-and-functions.adoc)"]

Virtual Machines

A Virtual Machine (VM) is the IaaS baseline: Azure provisions and patches the hypervisor and hardware, and everything above that — OS, runtime, patches, processes — is the operator’s responsibility. Reach for a VM when a workload needs full OS control, a specific kernel/driver combination, licensing tied to a particular OS image, or a lift-and-shift of an existing on-premises server with no appetite to re-platform it yet.

A VM is defined by four choices: an image (a marketplace, custom, or shared-gallery OS image), a size (the SKU that fixes vCPU/RAM/network/disk throughput, e.g. Standard_D2s_v5), one or more disks (an OS disk plus optional data disks, each with its own storage SKU — Standard HDD/SSD, Premium SSD, or Ultra Disk), and a network profile (a virtual network, subnet, and public/private IP).

Availability sets vs. availability zones

Both protect against correlated failure, at different scopes:

Availability set

A logical grouping that spreads a handful of VMs across separate fault domains (separate power/network within one datacenter) and update domains (so planned maintenance doesn’t reboot every instance at once), inside a single datacenter. Lower inter-VM latency than zones, and the cheaper choice when the region doesn’t need to survive a whole datacenter outage.

Availability zone

A physically separate group of one or more datacenters within a region, each with its own power, cooling and network. Spreading VMs (or a VM Scale Set) across zones survives the loss of an entire datacenter, at the cost of slightly higher inter-zone latency; it requires a region that supports zones.

A VM can use one or the other, never both. Bookshelf’s own workloads run on the container platforms covered below and don’t use either directly, but a VM standing in for a legacy dependency (e.g. a licensed appliance that must run on a VM) would pick zones in any region that offers them, and an availability set only as a fallback in a region that doesn’t.

In the Azure portal

  1. From the portal’s search bar, open Virtual machines → Create → Azure virtual machine.

  2. On the Basics tab, pick the resource group, a VM name, region, Availability options (no infrastructure redundancy, availability zone, or availability set), image, size, and authentication type (SSH public key or password).

  3. On the Disks tab, choose the OS disk type and add any data disks.

  4. On the Networking tab, choose or create the virtual network/subnet and whether to attach a public IP.

  5. Review + create.

  6. Once created, open the VM’s blade → Operations → Auto-shutdown to schedule a daily shutdown time, time zone, and an optional pre-shutdown notification.

With the Azure CLI

export RG=rg-bookshelf
export LOCATION=eastus2

az group create --name $RG --location $LOCATION

# A single VM in an availability zone (only in regions that support zones)
az vm create \
  --resource-group $RG \
  --name vm-bookshelf-legacy \
  --image Ubuntu2404 \
  --size Standard_D2s_v5 \
  --zone 1 \
  --generate-ssh-keys

# The availability-set alternative, when the region has no zones
az vm availability-set create --resource-group $RG --name avset-bookshelf --platform-fault-domain-count 2 --platform-update-domain-count 5
az vm create \
  --resource-group $RG \
  --name vm-bookshelf-legacy2 \
  --image Ubuntu2404 \
  --size Standard_D2s_v5 \
  --availability-set avset-bookshelf \
  --generate-ssh-keys

Schedule a daily auto-shutdown from the CLI with az vm auto-shutdown — the same Microsoft.DevTestLab/schedules resource the portal’s Auto-shutdown blade configures. --time is a UTC time of day in hhmm form, --email (or --webhook) sends a notification shortly before the shutdown, and --off clears the schedule again. For an on-demand stop outside the schedule, use az vm deallocate rather than an in-guest shutdown: a deallocated VM stops compute billing entirely, whereas an in-guest shutdown (shutdown -h inside the OS) leaves the VM allocated and still billed for compute.

# Shut vm-bookshelf-legacy down every day at 19:00 UTC, emailing a warning beforehand
az vm auto-shutdown --resource-group $RG --name vm-bookshelf-legacy --time 1900 --email ops@bookshelf.example.com

# Remove the schedule
az vm auto-shutdown --resource-group $RG --name vm-bookshelf-legacy --off

# Deallocate immediately, outside the schedule
az vm deallocate --resource-group $RG --name vm-bookshelf-legacy

Virtual Machine Scale Sets

A Virtual Machine Scale Set (VMSS) is a group of identical, load-balanced VMs managed as one resource, created from the same image/size/network profile and scaled in or out either manually or by an autoscale rule attached through Azure Monitor. It is the IaaS answer to "many interchangeable VM instances" the way an availability set or zone is the answer to "a handful of specific ones" — a scale set can itself span zones for the same datacenter-loss protection described above, at the group level rather than per instance.

In the Azure portal

  1. From the search bar, open Virtual machine scale sets → Create.

  2. Fill in the same Basics/Disks/Networking tabs as a single VM, plus an Instance count on the Scaling tab and, optionally, availability zones there too.

  3. On the Scaling tab, toggle Autoscale and define scale-out/scale-in rules against a metric (typically average CPU percentage) with a minimum, maximum and default instance count.

With the Azure CLI

export SS=vmss-bookshelf-legacy

az vmss create \
  --resource-group $RG \
  --name $SS \
  --image Ubuntu2404 \
  --instance-count 2 \
  --zones 1 2 3 \
  --generate-ssh-keys

# Scale out above 70% average CPU, scale in below 30%
az monitor autoscale create \
  --resource-group $RG \
  --resource $SS --resource-type Microsoft.Compute/virtualMachineScaleSets \
  --name autoscale-bookshelf-legacy \
  --min-count 2 --max-count 10 --count 2

az monitor autoscale rule create \
  --resource-group $RG --autoscale-name autoscale-bookshelf-legacy \
  --condition "Percentage CPU > 70 avg 5m" \
  --scale out 3

az monitor autoscale rule create \
  --resource-group $RG --autoscale-name autoscale-bookshelf-legacy \
  --condition "Percentage CPU < 30 avg 5m" \
  --scale in 1

Azure Virtual Desktop, in brief

Azure Virtual Desktop is a desktop- and app-virtualization service, not a general compute target for backend workloads: it streams full Windows 11/10 desktops or individual RemoteApp applications — including a multi-session Windows configuration that lets many users share one underlying VM’s OS overhead — to any device over the same Remote Desktop Protocol stack Windows admins already know, with Azure hosting the session hosts (themselves ordinary VMs, sized and imaged as above) plus the connection broker, gateway and load-balancing control plane so nothing outside those session-host VMs needs to be run or patched directly. It belongs on this page only because the decision tree’s "need full OS control" branch forks here: pick Azure Virtual Desktop over a plain VM specifically when the requirement is an interactive remote desktop or published app for end users, not a headless service.

Azure Container Instances

Azure Container Instances (ACI) runs one or a handful of containers directly, with no VM to provision or patch and no orchestrator to run: az container create returns a running container group in seconds, billed per second for the CPU/memory actually requested. It fits a short-lived job, a burst of parallel work, a build agent, or any single/few-container task that doesn’t justify Container Apps' or AKS’s always-on control plane — compare with Container Apps and AKS Clusters once a workload grows past that.

In the Azure portal

  1. From the search bar, open Container Instances → Create.

  2. On the Basics tab, pick the resource group, container name, region, image source and image.

  3. On the Advanced tab, set environment variables and a restart policy; on Networking, choose a public IP, DNS name label, or none.

  4. To use Spot pricing, set Availability to Spot on the Basics tab before creating.

With the Azure CLI

export RG=rg-bookshelf

az container create \
  --resource-group $RG \
  --name notifier-oneoff \
  --image mcr.microsoft.com/azuredocs/aci-helloworld \
  --cpu 1 --memory 1.5 \
  --restart-policy OnFailure

az container logs --resource-group $RG --name notifier-oneoff

Spot containers trade eviction risk for a steep discount, using unused Azure capacity the same way Spot VMs do; request one with --priority spot, and note that a Spot container group cannot expose a public IP or DNS name label, so it only suits workloads that are polled or pull work from a queue rather than ones addressed directly:

az container create \
  --resource-group $RG \
  --name notifier-spot \
  --image mcr.microsoft.com/azuredocs/aci-helloworld \
  --priority spot

Confidential containers run the container group inside a hardware-backed, AMD SEV-SNP Trusted Execution Environment, encrypting data in use and letting a relying party verify the running code via remote attestation before releasing a secret to it — worthwhile for multi-party data processing or model-inference workloads where even Azure’s own operators shouldn’t see the data in memory, at the cost of authoring a confidential computing enforcement (CCE) policy with the separate confcom CLI extension. NGroups is a preview feature layered on top of ACI that manages many identical container groups as one resource — a container group profile template plus a desired count, with rolling updates, zone spread and automatic replenishment — and is the piece that will most directly matter here once it’s no longer preview, since it’s the closest ACI equivalent to a Container Apps replica set or a Scale Set. Both remain evolving previews at the time of writing: confirm current region/OS support and preview status against the pages below before depending on either in production.

Clean up

Remove everything created on this page in one step once done experimenting:

az group delete --name $RG --yes --no-wait

If a VM created above was left running under a different resource group, deallocate it first so compute billing stops immediately rather than waiting on the group deletion to finish:

az vm deallocate --resource-group $RG --name vm-bookshelf-legacy

References: az group delete.

Comparing the compute options

The table below covers control, scaling, scale-to-zero, networking, pricing model and the short version of "when to use it" for every compute option this documentation section touches. Each PaaS/container row links to the page that covers it hands-on.

Service Control Scaling Scale-to-zero Networking Pricing model When to use

Container Apps

Managed (Dapr, revisions, VNet integration exposed as config)

Automatic, KEDA-based, on HTTP/CPU/custom metrics

Yes

VNet integration, internal/external ingress

Consumption (vCPU-seconds/GiB-seconds) or dedicated plan

A microservice needing Dapr, jobs, multiple revisions, or VNet-level control that plain HTTP scaling doesn’t need

Container Apps Express

Minimal (no environment step, fewer knobs)

Automatic, HTTP-first

Yes, sub-second cold start

Internal or external HTTP ingress and VNet egress; no custom domains

Consumption

Fastest path from a container image to a production HTTP endpoint, no Dapr/jobs/custom domains required

App Service

Managed PaaS (App Service Plan sizes the VMs underneath)

Manual or autoscale rules against the Plan

Only on Consumption-adjacent plans; not on Basic/Standard

VNet integration on higher plan tiers

Per-Plan-instance (fixed tiers) or consumption on some SKUs

A classic web app/API that fits the App Service model directly, without containers-first tooling

Functions

Fully managed (event-driven runtime, triggers/bindings)

Automatic, per-event

Yes, on the Consumption plan

VNet integration on Premium/Dedicated plans

Per-execution (Consumption) or Plan-based (Premium/Dedicated)

Event-driven, short-lived work — a queue trigger, a webhook, a timer job

AKS

Full (Kubernetes API, CRDs, operators, service mesh, node pools)

Cluster autoscaler, HPA/VPA, KEDA, Karpenter-style node provisioning

Only with scale-to-zero-capable node pools/KEDA, not by default

Full VNet/CNI control, network policies, private cluster

Per-node compute + cluster management (free/standard/premium tier)

Multiple interdependent microservices needing Kubernetes primitives Container Apps doesn’t expose

Azure Container Instances

Minimal (single/few containers, no orchestrator)

Manual only, one group at a time (NGroups, preview, adds a desired count)

Not applicable — billed per second while running, delete when done

Public IP/DNS label or private VNet attach; Spot groups can’t expose either

Per-second CPU/memory, or discounted Spot pricing

A short-lived job, burst task, or build agent that doesn’t justify an always-on control plane

Virtual Machines / Scale Sets

Full (OS, kernel, patching, everything above the hypervisor)

Manual (VM) or autoscale rules (Scale Set)

No — always billed while allocated, deallocate explicitly to stop billing

Full VNet/NSG/route-table control

Per-second compute by size/SKU, plus attached disk/network costs

Full OS control, legacy/lift-and-shift workloads, or a specific licensed/kernel requirement