Azure VM stopped vs deallocated: stopped still bills

You shut an Azure VM down last month and assumed the bill stopped with it. Open the cost analysis and the compute charge is still there, every hour, for a machine nobody has touched. A stopped Azure VM keeps its CPU and memory reserved, so Azure keeps charging you 100% of the compute rate until you release it. Deallocate the VM and that compute charge drops to zero. The fix is one word, and most teams have never run it.
Why a stopped VM still costs full price
Azure has two different off states, and only one of them stops the compute meter.
When you shut a VM down from inside the guest OS, the machine powers off but Azure keeps the physical host reserved for it. That state is called Stopped (allocated). The vCPUs and RAM are still set aside for you, so you keep paying the full compute rate as if the VM were running.
Deallocation is the state that saves money. When a VM is deallocated, Azure releases the compute back to the pool. You stop paying for vCPUs and memory completely. You keep paying only for the managed disks attached to the VM, because those still exist on storage whether the VM runs or not.
Here are the two off states side by side.
| State | How you get there | Compute billed | Disks billed | Dynamic public IP | Temp disk |
|---|---|---|---|---|---|
| Stopped (allocated) | Guest OS shutdown, az vm stop, Stop-AzVM -StayProvisioned |
Yes, full rate | Yes | Kept | Kept |
| Deallocated | Portal Stop, az vm deallocate, Stop-AzVM |
No | Yes | Released | Wiped |
Put real numbers on it. As of June 2026, a Standard_D4s_v5 (4 vCPU, 16 GiB) lists around $0.192 per hour for pay-as-you-go Linux in East US on the Azure VM pricing page, which is about $140 per month. Its 128 GiB Premium SSD P10 OS disk lists around $19.71 per month on the managed disks pricing page. Leave that VM Stopped (allocated) and you pay the full $140 of compute for a machine doing no work. Deallocate it and compute drops to zero, leaving roughly $20 a month for the disk you chose to keep.
Windows VMs make the trap cost more. Azure folds the Windows Server license into the per-hour rate, so a Windows box bills for the operating system on top of the hardware. Leave it Stopped (allocated) and you keep paying both the compute and the license for a machine doing nothing. Deallocate it and the whole meter stops, license included. The exception is Azure Hybrid Benefit: bring your own Windows Server license and you already dropped that charge, so a stopped Windows VM wastes only the hardware rate.
One caveat if you run Azure Reservations or a savings plan. That commitment bills whether the VM runs or not, so deallocating a covered VM only saves money when the reservation moves to another running VM of the same size. For pay-as-you-go VMs, the full compute charge simply stops.
The trap: "Stop" does not mean deallocate
Here is the part that catches people, and it catches them through the tools they trust most.
Shutting down from inside the operating system never deallocates. The guest has no way to tell Azure to release the host, so the VM sits in Stopped (allocated) and bills on. A shutdown /s on Windows or a shutdown on Linux saves you nothing.
The Azure CLI sets the same trap. az vm stop powers the VM off but leaves it allocated and billing. The command that actually stops the meter is az vm deallocate. Two commands that sound interchangeable, one of them still charges you.
Azure PowerShell flips the trap around. Stop-AzVM on its own deallocates the VM, so the default does the right thing and the meter stops. Add -StayProvisioned and it stops the VM without deallocating, and Azure keeps charging you for the stopped machine. The safe command and the expensive one sit one flag apart, so an old runbook that still passes -StayProvisioned keeps paying full compute for a box that does nothing.
The portal is the exception. The Stop button in the Azure portal deallocates the VM for you. So a team that stops VMs by clicking in the portal pays nothing extra, while a team that automated the same task with az vm stop or a guest shutdown script has been paying full compute the whole time.
Find your stopped-but-billing VMs by hand
Power state does not appear in a plain az vm list. You need the -d flag, which pulls the instance view and includes powerState.
List every VM with its power state in the current subscription:
az vm list -d \
--query "[].{name:name, rg:resourceGroup, state:powerState}" \
--output table
The state you are hunting is VM stopped. That is the allocated-but-off state that still bills compute. VM deallocated is the safe one, and VM running is working as intended.
The exact string changes with the surface you read it on, which trips people up when they search for it. The portal shows Stopped (allocated) and Stopped (deallocated). The CLI returns VM stopped and VM deallocated. The REST API and ARM templates use PowerState/stopped and PowerState/deallocated. All three describe the same two states.
Azure Advisor flags low-use running VMs under its cost recommendations, but a VM that is stopped and still allocated can slip past it, so the sweep below is worth running yourself.
Filter straight to the VMs that are costing you money for nothing:
az vm list -d \
--query "[?powerState=='VM stopped'].{name:name, rg:resourceGroup, size:hardwareProfile.vmSize}" \
--output table
Every row is a VM you are paying full compute for while it does no work. The size column tells you which ones hurt most, since a stopped GPU or memory-optimized VM burns far more than a stopped B-series box.
The query is scoped to one subscription at a time, so sweep every subscription you own in a loop:
for sub in $(az account list --query "[].id" -o tsv); do
az vm list -d --subscription "$sub" \
--query "[?powerState=='VM stopped'].{name:name, rg:resourceGroup, size:hardwareProfile.vmSize}" \
--output table
done
Sweep every subscription in one query
That loop works, and it gets slow. Each subscription is a separate round trip, and every -d call pulls the instance view for each VM inside it. Across twenty subscriptions you are waiting minutes for an answer you wanted in one shot.
Azure Resource Graph does the whole sweep in a single query. It reads a pre-indexed copy of your resource metadata, so it returns in about a second regardless of how many subscriptions you own.
Add the extension once:
az extension add --name resource-graph
Then ask for every VM sitting in the allocated-but-off state:
az graph query -q "
resources
| where type =~ 'microsoft.compute/virtualmachines'
| extend powerState = tostring(properties.extended.instanceView.powerState.code)
| where powerState == 'PowerState/stopped'
| project name, resourceGroup, subscriptionId, vmSize = tostring(properties.hardwareProfile.vmSize)
| order by vmSize asc
" --output table
PowerState/stopped is the state that still bills compute. PowerState/deallocated is the safe one. Those are the same two states the CLI prints as VM stopped and VM deallocated, spelled the way the REST API writes them.
Two things will bite you here. Resource Graph returns results for every subscription your account can read, so pass --subscriptions when you want to narrow the blast radius of the report. And property paths under properties are case sensitive, so a lowercase powerstate returns empty results instead of an error, which reads exactly like a clean bill of health.
Find and deallocate them from Azure PowerShell
Every command above is Azure CLI. If your runbooks are PowerShell, the cmdlets do the same work, and the discovery step hides a trap of its own.
Get-AzVM returns the model view by default, and the model view carries no power state. Add -Status and every VM comes back with a PowerState property. Leave the switch off and that property is absent rather than empty, so a filter on it quietly matches nothing and reads like good news.
Get-AzVM -Status |
Where-Object { $_.PowerState -eq 'VM stopped' } |
Select-Object Name, ResourceGroupName, PowerState
VM stopped is the allocated-but-off state that still bills compute, and VM deallocated is the safe one. Those are the same two strings az vm list -d prints, because both read the same instance view.
Resource Graph ships a cmdlet too, so the cross-subscription sweep is one call here as well. Install the module once:
Install-Module -Name Az.ResourceGraph -Scope CurrentUser
Then run the query from the previous section:
Search-AzGraph -UseTenantScope -First 1000 -Query "
resources
| where type =~ 'microsoft.compute/virtualmachines'
| extend powerState = tostring(properties.extended.instanceView.powerState.code)
| where powerState == 'PowerState/stopped'
| project name, resourceGroup, subscriptionId, vmSize = tostring(properties.hardwareProfile.vmSize)
"
Two flags there are load-bearing. -UseTenantScope runs the query across every subscription in the tenant, which is the job the CLI loop was doing. And -First defaults to 100, so a tenant with more than a hundred VMs returns a truncated list that looks exactly like a complete one. The ceiling is 1000, and past that you page with -SkipToken.
Deallocating is a single cmdlet, and the default is the safe one.
Stop-AzVM -ResourceGroupName my-rg -Name my-vm -Force
Stop-AzVM deallocates unless you add -StayProvisioned, and -Force skips the confirmation prompt so the command runs unattended. Starting the VM again is the mirror image.
Start-AzVM -ResourceGroupName my-rg -Name my-vm
The public IP and temporary disk checks further down apply the same way, whichever tool you drive.
The scale set instances the sweeps never return
Every sweep so far asks Azure for virtual machines. A scale set instance might not be one of them.
Scale sets carry the same two off states, behind the same pair of commands that sound interchangeable. az vmss stop powers instances down and leaves them allocated, so compute keeps billing. az vmss deallocate releases the compute, and the REST reference states you are not billed for the capacity it hands back. On one VM that mistake costs the $140 a month from earlier. On a scale set it costs that per instance.
Whether the sweeps above already found them comes down to how the scale set was created.
Flexible orchestration instances are ordinary Azure VMs. Microsoft's orchestration modes comparison gives their virtual machine type as Microsoft.Compute/virtualMachines, the same type a standalone VM carries. Every command above already returns them, so there is nothing extra to run.
Uniform orchestration instances are a different resource type, Microsoft.Compute/virtualMachineScaleSets/virtualMachines, and the same page records that those instances are not compatible with the standard Azure IaaS VM commands. So az vm list -d skips them, and Get-AzVM -Status skips them. The Resource Graph sweep skips them for a second reason. Uniform instances are absent from the resources table altogether, and sit in a separate table called ComputeResources.
Query that table to find the ones stopped and still allocated.
az graph query -q "
ComputeResources
| where type =~ 'microsoft.compute/virtualmachinescalesets/virtualmachines'
| extend powerState = tostring(properties.extended.instanceView.powerState.code)
| where powerState == 'PowerState/stopped'
| project name, resourceGroup, subscriptionId
" --output table
Run AKS and you own this population without having picked it. The comparison table above lists AKS as supported on Uniform and unsupported on Flexible, so every node pool you have is a Uniform scale set, and none of those nodes appear in a microsoft.compute/virtualmachines sweep.
For a single scale set, the CLI reads every instance in one call. Passing --instance-id "*" is the documented way to ask for all of them at once.
az vmss get-instance-view \
--resource-group my-rg \
--name my-vmss \
--instance-id "*" \
--output table
Then release the instances you do not need, either by id or by passing * for the whole set.
az vmss deallocate \
--resource-group my-rg \
--name my-vmss \
--instance-ids 3 7
Deallocating node pool instances underneath AKS takes capacity away from a control plane that still believes it has nodes. Scale the node pool with az aks nodepool scale instead, and let AKS retire the instances itself.
Deallocate without surprises
Once you have the list, deallocating is one command per VM.
az vm deallocate \
--resource-group my-rg \
--name my-vm
The VM moves to VM deallocated, the compute charge stops on the next billing interval, and the disks stay exactly where they are. Starting it again is just as direct.
az vm start \
--resource-group my-rg \
--name my-vm
That is your rollback. Deallocation does nothing destructive to your managed disks, so bringing a VM back is a single start call.
Two things are worth checking before you deallocate, because both can bite on restart.
First, the public IP. A dynamic public IP is released when a VM deallocates, and the VM can come back on a different address. If anything points at that IP (DNS records, a firewall allowlist, a partner's config), convert it to a static address first so it survives the restart (the update below changes the allocation method only, so a static IP keeps the same address across a deallocate and start). A static Standard IP does carry a small standing charge while the VM is down, which the billing section below covers.
az network public-ip update \
--resource-group my-rg \
--name my-vm-ip \
--allocation-method Static
Second, the temporary disk. The local temp disk (D: on Windows, /dev/sdb1 on Linux) is wiped when a VM deallocates. It is meant for scratch data like page files and caches, so confirm nothing important lives there before you release the host.
One more thing for specialty hardware. Deallocation returns the VM's compute capacity to the regional pool. For common sizes that is fine. For scarce SKUs like GPU or HPC series in a busy region, the same size may not be free when you try to start the VM again. If a machine has to come back on demand, a capacity reservation holds the size for you while it sits deallocated.
The VM that will not deallocate
Some VMs have no deallocated state to move to, and Azure only says so when you try.
A VM built with an ephemeral OS disk keeps its operating system on the host's local SSD instead of on managed disks. Releasing the host would take the OS disk with it, so Azure refuses the request outright with Operation 'deallocate' is not supported for VMs or VM Scale Set instances using an ephemeral OS disk. The ephemeral OS disks documentation lists the stop-deallocated state as "Not Supported" for these VMs, along with plain stop and start.
This is a different thing from the temporary disk in the previous section. The temp disk is scratch space sitting next to a normal OS disk. An ephemeral OS disk is the OS disk itself, living on local storage.
Nothing above will surface these. The sweeps hunt for machines already sitting in VM stopped, and an ephemeral VM can never be in that state. Its waste has a different shape: a running box you cannot switch off.
Find them by the OS disk setting that defines them. diffDiskSettings.option reads Local on an ephemeral VM and is absent on every other one.
az vm list \
--query "[?storageProfile.osDisk.diffDiskSettings.option=='Local'].{name:name, rg:resourceGroup, size:hardwareProfile.vmSize}" \
--output table
Resource Graph runs the same filter across every subscription at once.
az graph query -q "
resources
| where type =~ 'microsoft.compute/virtualmachines'
| extend diffDisk = tostring(properties.storageProfile.osDisk.diffDiskSettings.option)
| where diffDisk == 'Local'
| project name, resourceGroup, subscriptionId, vmSize = tostring(properties.hardwareProfile.vmSize)
" --output table
Deleting is the only thing that stops the compute charge on one of these. That is a smaller decision than it sounds. An ephemeral OS disk was never holding state you meant to keep, because Azure reprovisions the OS from the image whenever the VM is reimaged, resized, or redeployed. Rebuilding one is a template run rather than a restore.
Converting is off the table. Ephemeral OS disks are set at creation time only, so putting an existing VM onto a persistent OS disk means recreating it. If a machine turns out to need an off switch, rebuild it with a managed OS disk and it joins the deallocation candidates like any other VM.
Deallocated but the bill did not drop to zero
Deallocation stops the compute meter. It does not stop every meter. If you release a VM and the monthly cost only falls part way, here is what is still charging.
The managed disks keep billing. They sit on storage whether the VM runs or not, so the 128 GiB P10 OS disk from earlier still costs about $20 a month. That charge is expected, and it is the price of keeping the VM ready to start.
A static public IP keeps billing too, and this one catches people out. When you made the IP static so it would survive the restart, you handed Azure a resource it charges for on its own. A Standard static public IP lists around $0.005 an hour, about $3.65 a month, and it bills whether or not a running VM is attached to it. So a deallocated VM with a static IP still pays for the address. Basic SKU public IPs, which used to be the cheap way around this, were retired on 30 September 2025, so Standard is the only SKU you can create now.
A reservation or savings plan keeps billing. That commitment is a prepayment for a running VM of a given size, so it charges on whether or not this particular VM runs. Deallocating a covered VM saves money only once the reservation lands on another running VM of the same size.
Any snapshots or images you took of the disks bill as storage as well, at the snapshot rate for their size.
Here is what changes the moment a pay-as-you-go VM deallocates.
| Charge | Stopped (allocated) | Deallocated |
|---|---|---|
| Compute (vCPU + RAM) | Full rate | Stops |
| Windows license (no Hybrid Benefit) | Full rate | Stops |
| Managed disks | Billing | Billing |
| Standard static public IP | Billing | Billing |
| Reservation or savings plan | Billing | Billing |
The compute line is the big one, and it is the line that drops to zero. The rest is the standing cost of keeping the VM and its address ready to come back. If you are done with the VM for good, delete the disks and release the static IP so those meters stop too.
Deallocate or delete: pick the one you mean
Deallocate and delete both stop the compute charge. Only one of them lets you take it back.
Deallocate keeps the VM. The configuration, the disks, and the network wiring all stay in place, so a single az vm start brings the machine back exactly as it was. Reach for it whenever there is any chance you want the VM again, even months out.
Delete is for VMs you are finished with for good. It stops compute the same way, and it can stop the disk meter too, but only if you remember to remove the disks. Here is where it bites: az vm delete removes the VM object and nothing else.
# Deletes the VM only. Disks, NIC, and public IP stay behind and keep billing.
az vm delete --resource-group my-rg --name my-vm --yes
The OS disk, any data disks, the NIC, and a static public IP all outlive the VM as orphaned resources, and every one of them keeps charging. So a cleanup that ran nothing but az vm delete leaves a pile of disks billing storage for a machine that no longer exists. That is the same trap as the stopped VM, one step further along.
Find the disks that are now attached to nothing, then delete the ones you are sure about.
az disk list \
--query "[?managedBy==null].{name:name, rg:resourceGroup, sizeGb:diskSizeGb}" \
--output table
If you would rather not clean up by hand every time, set the delete options when you build the VM (--os-disk-delete-option Delete, --data-disk-delete-option Delete, --nic-delete-option Delete) so the disks and NIC leave when the VM does. When in doubt, deallocate. Delete is the door that does not open from the other side.
Put the deallocation on a schedule
Sweeping for stopped VMs cleans up what already went wrong. It does nothing about the next dev box someone powers off from the guest OS in three weeks. If you want the waste to stay gone, put the deallocation on a clock.
Azure ships a schedule on every VM. Auto-shutdown fires once a day at a time you set and deallocates the machine, so it stops the compute meter rather than parking the VM in the allocated state.
az vm auto-shutdown \
--resource-group my-rg \
--name my-vm \
--time 0200
Clear it again with --off, and add --email if you want a warning before it fires.
az vm auto-shutdown \
--resource-group my-rg \
--name my-vm \
--off
Two things about this feature catch people, and both cost you the hours you thought you were saving.
The clock is the first one. The CLI reference describes --time as "The UTC time of day the schedule will occur every day", in hhmm form, and the command has no timezone flag at all. The portal does give you a timezone picker, and it starts on UTC. So a team in Los Angeles that sets --time 1800 expecting an evening shutdown gets one at 11am local, in the middle of the working day, on every VM they just scripted. Set the hour from the portal when you want local time, or write timeZoneId directly on the underlying Microsoft.DevTestLab/schedules resource, which is where the portal picker stores it.
The second is that the schedule only runs one way. Auto-shutdown deallocates on a timer and Azure gives you no built-in auto-start to match it. Whoever needs the machine in the morning runs az vm start by hand. To automate both ends you need Azure Automation, a Logic App, or the Start/Stop VMs during off-hours solution, and each of those is a separate thing to deploy and keep working.
Point it at dev, test, build agents, and demo boxes, the machines with an obvious overnight. Leave it off anything that runs a nightly job, because a schedule that deallocates a VM at 2am is indistinguishable from an outage to whoever owns that job.
A quick decision tree
graph TD
A[VM doing no work] --> B{Need it again soon?}
B -- No --> C[Delete the VM and its disks]
B -- Yes --> D{Power state from az vm list -d or Get-AzVM -Status}
D -- VM deallocated --> E[Already not billing compute]
D -- VM stopped --> F{Dynamic public IP or temp-disk data to keep?}
D -- VM running --> I[Still powered on, so deallocate stops it. Confirm it is truly idle first]
I --> F
F -- Yes --> G[Make the IP static, a small monthly charge, and move temp data first]
F -- No --> H[Run az vm deallocate now]
G --> H
H --> M{Refused as an ephemeral OS disk?}
M -- Yes --> N[No deallocated state exists. Delete and rebuild from the image]
M -- No --> J{Idle on a predictable schedule?}
J -- Yes --> K[Set az vm auto-shutdown, remembering the time is UTC, and plan who starts it]
J -- No --> L[Re-run the sweep next month]
Related reading
The same waste hides wherever you reserve capacity and forget to use it.
- Cut Kubernetes costs by right-sizing pod requests is the pod version of this bill. You pay for every CPU and memory request a pod makes, whether it uses them or not.
- Migrate gp2 EBS volumes to gp3 is the storage side of the fight over on AWS. A deallocated VM keeps billing for its disks, so a cheaper volume type is money you get back.
- Snowflake AUTO_SUSPEND: stop paying for idle warehouses is the same trap in your data warehouse. A running warehouse bills credits every second it waits for a query, so the idle timeout you set is the bill you pay.
- Find unused Postgres indexes and stop paying to keep them is the database version. An index nobody reads still takes storage on every replica, and it taxes every write that has to keep it current.
- Cut Datadog log costs: indexing bills 17x ingestion is the telemetry version of the same reservation. Indexing buys you a log you can search later, at about 17 times the price of ingesting it, and the noisy sources nobody has ever queried pay that premium every day they stay in the index.
- Cloud waste is a gross margin problem is the reason any of this gets funded. Your cloud bill is cost of revenue, so the hours a stopped VM bills come straight out of gross profit.
Common questions
Does a stopped Azure VM still cost money?
If it reads Stopped (allocated), yes. Azure holds the vCPUs and memory for that VM, so the full compute rate keeps billing until you deallocate it. A Stopped (deallocated) VM bills nothing for compute.
What is the difference between stopped and deallocated?
Stopped means the VM is powered off but its host is still reserved for you, so compute keeps billing. Deallocated means the host goes back to Azure, so compute drops to zero, unless an Azure Reservation or savings plan covers the VM, in which case that commitment keeps billing until it moves to another running VM of the same size. Either way the managed disks keep billing at their usual rate.
How do I stop paying for an Azure VM I am not using?
Deallocate it with az vm deallocate, or hit the portal Stop button, which deallocates for you. If you will never need the VM again, delete it and its disks so the storage charge stops too.
Do I still pay for storage after deallocating?
Yes. The managed disks attached to the VM sit on storage whether the VM runs or not, so they bill every month until you delete them. Only the compute charge stops when you deallocate.
Why is my deallocated Azure VM still charging me?
Compute stops when you deallocate, but a few meters keep running. The managed disks bill as storage, a Standard static public IP bills about $3.65 a month whether a VM is attached or not, and any Azure Reservation or savings plan keeps charging until it covers another running VM of the same size. Delete the disks and release the static IP if you are finished with the VM for good.
Does Azure auto-shutdown deallocate the VM?
The built-in Auto-shutdown schedule on a VM deallocates it, so it does stop the compute bill on the hours you set. A shutdown triggered from inside the guest OS leaves the VM Stopped (allocated) and billing, so confirm which one your automation actually runs.
Why did my Azure auto-shutdown schedule fire at the wrong time?
Almost always the timezone. The az vm auto-shutdown --time flag takes a UTC hour in hhmm form and has no timezone option, and the portal picker also starts on UTC. A schedule set to 1800 from the CLI in US Pacific shuts the VM down at 11am local. Set the hour in the portal, or set timeZoneId on the Microsoft.DevTestLab/schedules resource behind it.
Can Azure start a VM automatically after auto-shutdown?
Not with the built-in feature. Auto-shutdown deallocates on a schedule and there is no matching auto-start, so the next person to need the VM runs az vm start. Scheduling both ends means Azure Automation, a Logic App, or the Start/Stop VMs during off-hours solution.
How do I find every stopped VM across all my Azure subscriptions at once?
Use Azure Resource Graph. A single az graph query filtering on properties.extended.instanceView.powerState.code == 'PowerState/stopped' covers every subscription your account can read and returns in about a second. Looping az vm list -d over each subscription gets the same answer, and it costs you a round trip per subscription.
How do I find stopped Azure VMs with PowerShell?
Run Get-AzVM -Status and filter on the power state, for example Where-Object { $_.PowerState -eq 'VM stopped' }. The -Status switch is the part people miss: without it the returned objects have no PowerState property at all, so the filter matches nothing and looks like a clean result. To cover every subscription in one call, use Search-AzGraph -UseTenantScope from the Az.ResourceGraph module, and raise -First from its default of 100 so a large tenant does not come back truncated.
Does Stop-AzVM deallocate the VM?
Yes, that is the default. Stop-AzVM -ResourceGroupName my-rg -Name my-vm releases the host, so the compute charge stops. Adding -StayProvisioned is what leaves the VM allocated and billing full compute, so grep your runbooks for that flag before you trust them.
Does az vmss stop deallocate scale set instances?
No. az vmss stop powers the instances off and leaves them allocated, so they keep billing compute exactly like a standalone VM in the Stopped (allocated) state. az vmss deallocate is the command that releases the capacity and stops the charge. The two sit one word apart, the same way az vm stop and az vm deallocate do.
Why do my AKS nodes not show up in az vm list?
Because AKS node pools run on Uniform orchestration scale sets, and Uniform instances are the resource type Microsoft.Compute/virtualMachineScaleSets/virtualMachines rather than Microsoft.Compute/virtualMachines. The standard VM commands do not read that type, so az vm list and a Resource Graph query against the resources table both come back without them. Query the ComputeResources table instead, or list the instances per scale set with az vmss get-instance-view --instance-id "*".
Does a stopped Windows VM still bill for the Windows license?
Yes, if it reads Stopped (allocated). The Windows Server license is baked into the VM's per-hour rate, so a stopped-but-allocated Windows VM keeps paying for the license along with the compute. Deallocate it and both charges stop. If you use Azure Hybrid Benefit, you are not paying the license on that VM to begin with, so only the hardware rate is at stake.
Why does az vm deallocate fail with "not supported for VMs using an ephemeral OS disk"?
Because that VM's operating system lives on the host's local storage rather than on managed disks. Deallocating hands the host back to Azure, which would take the OS disk with it, so the platform blocks the operation. A VM like that can be restarted, reimaged, or deleted, and deleting is the only one of the three that stops the compute charge.
Can I deallocate an Azure VM with an ephemeral OS disk to save money?
No. Stop-deallocate is unsupported on ephemeral OS disks, so nothing short of deleting the VM stops its compute meter. Rebuilding is cheaper than it sounds, since Azure reprovisions the OS from the image on every reimage, resize, and redeploy anyway. Ephemeral OS disks are also fixed at creation time, so moving a VM onto a persistent OS disk means recreating it. To find these VMs, filter on storageProfile.osDisk.diffDiskSettings.option == 'Local', which is absent on every VM with a normal managed OS disk.
Does deallocating an Azure VM delete it or lose my data?
No. Deallocating releases the host and stops the compute charge, and it leaves the VM, its managed disks, and its configuration in place. One az vm start brings it back as it was. Only the temporary disk is wiped, since that is scratch space by design.
What is the difference between deallocating and deleting an Azure VM?
Both stop the compute charge. Deallocating is reversible and keeps the disks, so you can start the VM again later. Deleting is permanent, and az vm delete removes only the VM object, so the managed disks, the NIC, and any static public IP stay behind and keep billing until you delete them too.
How OhChimp approaches this
A sweep clears today's list. Then a runbook calls az vm stop, or someone shuts the box down from inside the guest, and it lands back in Stopped (allocated). No alert fires for that.
OhChimp scans your Azure subscriptions, finds the VMs sitting in Stopped (allocated), and writes a reviewable plan that names the monthly compute you are wasting, with a confidence score, a risk level, and the rollback spelled out. The public IP and temp-disk questions are answered per VM, so you read a recommendation instead of redoing the checks. You click apply, nothing moves until you do, and the saving is then measured against your real Azure bill.
See how the Azure connection works on the Azure integration page.