The idea
A resource that runs weekdays 07:00–19:00 instead of 24×7 runs 60 hours instead of 168 and saves around 64% of its compute cost. Storage costs continue; compute stops.
1. Agree the schedule and tag it
| Tag | Example | Meaning |
|---|---|---|
environment | dev / test / prod | Never schedule prod |
schedule | weekdays-0700-1900 | When it should run |
schedule-tz | India Standard Time | Time zone of the owners |
schedule-override-until | 2026-10-31 | Opt-out for a deadline |
Enforce the environment tag with Azure Policy so the automation knows what it may touch.
2. VMs
Simplest, per VM: auto-shutdown (no auto-start):
az vm auto-shutdown -g rg-dev -n dev-app01 --time 1900 --email "team-dev@example.com"
--time is in UTC unless set otherwise in the portal; check the time zone on the schedule.
For start and stop by tag across subscriptions, use Start/Stop VMs v2 (Functions + Logic Apps solution from Microsoft) or an Automation runbook:
param([ValidateSet('Start','Stop')] [string]$Action)
Connect-AzAccount -Identity
$vms = Get-AzVM -Status | Where-Object { $_.Tags['environment'] -in 'dev','test' -and $_.Tags['schedule'] }
foreach ($vm in $vms) {
$until = $vm.Tags['schedule-override-until']
if ($Action -eq 'Stop' -and $until -and [datetime]$until -ge (Get-Date).Date) { continue }
if ($Action -eq 'Stop') { Stop-AzVM -ResourceGroupName $vm.ResourceGroupName -Name $vm.Name -Force -NoWait }
if ($Action -eq 'Start') { Start-AzVM -ResourceGroupName $vm.ResourceGroupName -Name $vm.Name -NoWait }
}
Stop-AzVM deallocates (compute billing stops). Shutting down from inside the OS doesn't.
3. Other resources
| Resource | Stop | Start | Note |
|---|---|---|---|
| AKS cluster | az aks stop -g rg -n aks-dev | az aks start -g rg -n aks-dev | Control plane and nodes stop; start takes minutes |
| Application Gateway | az network application-gateway stop -g rg -n agw-dev | ... start | Billing stops while stopped |
| Azure SQL Database | Serverless with auto-pause | Resumes on connect | See the elastic pools / serverless post |
| PostgreSQL / MySQL flexible server | az postgres flexible-server stop -g rg -n pg-dev | ... start | Auto-starts again after 7 days stopped |
| Azure Firewall (non-prod) | Deallocate via PowerShell | Allocate | See the firewall optimization post |
| VM Scale Sets | az vmss deallocate | az vmss start | Or scale to 0 |
| App Service | Scale plan down; stopping an app doesn't stop plan billing | Scale up | Consider a shared dev plan |
4. Schedule it
Azure Automation account with a system-assigned managed identity (role: Virtual Machine Contributor, plus specific roles for AKS/App Gateway/databases at the dev subscription scope). Two schedules per time zone: Start 06:45 and Stop 19:15, weekdays only.
5. Opt-out that people actually use
- A tag (
schedule-override-until) people can set themselves, with a maximum of a week or two. - A Teams/Slack message at 18:30: "These resources stop at 19:15 – set the override tag to keep them running."
- Never stop resources mid-deployment: check for running pipelines if your environments are deployed by CI.
6. Watch for side effects
- Scheduled jobs on dev VMs (backups, agents, overnight test runs) need moving into working hours.
- Dynamic public IPs change on deallocation; use static IPs or DNS where needed.
- Licences tied to running hosts, and services that must stay up for others (shared dev databases), get excluded explicitly.
7. Report savings
Monthly: compute hours avoided × hourly rate per resource (or compare Cost Management for the dev subscriptions month on month). Sharing the number keeps the schedule in place.
Tip
Start with a pilot subscription for two weeks. The first run always finds a VM that "must" stay on. It's better to discover it in one team than across the company.
Checklist
- Schedule and environment tags agreed and enforced
- VM start/stop by tag (Start/Stop v2 or runbook)
- AKS, App Gateway, databases and firewalls covered
- Managed identity with least-privilege roles
- Opt-out tag and pre-stop notification
- Side effects checked; savings reported monthly