FinOps / Guide 4
Power-off schedules
Turning off non-production outside working hours: how much it saves, the tools in each cloud, and what keeps billing.
A VM bills for every hour it runs, whether anyone is using it or not. Development, test, training and demo environments are typically used during working hours on weekdays, yet many run 24 hours a day, 7 days a week. Turning them off outside working hours is one of the easiest savings in the cloud, and it needs no change to the application.
How much it saves
A week has 168 hours. A schedule of 7:00 to 19:00, Monday to Friday, runs a VM for 60 of them.
| Schedule | Hours running per week | Compute saving |
|---|---|---|
| Always on | 168 | 0% |
| Weekdays 7:00 to 19:00 | 60 | about 64% |
| Weekdays 8:00 to 18:00 | 50 | about 70% |
| Off at weekends only | 120 | about 29% |
| On only when someone starts it | varies | often 80% or more |
The saving applies to compute. Disks, snapshots and reserved IP addresses keep billing while the VM is off (see the last section).
Choose what to schedule
- Good candidates: development, test, QA, training and demo VMs; build agents; jump boxes; anything tagged with a non-production environment.
- Usually not: production, domain controllers, anything other systems depend on overnight (batch, backups, replication targets), and VMs covered by a reservation or committed use discount (you pay for those whether they run or not).
Agree the hours with the team, and give them a way to start a VM out of hours when they need it. A schedule that blocks work gets switched off by the first frustrated engineer.
Use a tag to drive the schedule
Put the schedule on the resource as a tag (Azure, AWS) or label (Google Cloud), so the owner can see it and change it, and automation can act on it. For example:
| Tag or label | Value | Meaning |
|---|---|---|
Schedule / schedule | office-hours | Run weekdays 7:00 to 19:00 local time |
weekdays | Off at weekends only | |
manual | Stop every evening; start on request | |
always-on | Excluded from scheduling (record why in another tag) |
Tools in each cloud
| Azure | AWS | Google Cloud | |
|---|---|---|---|
| Built-in, per VM | Auto-shutdown on each VM (stop only, at a set time) | - | Instance schedules: a resource policy with start and stop times, attached to VMs |
| Fleet-wide, tag-driven | Start/Stop VMs v2 (Azure Functions; Microsoft now only maintains it), Azure Automation, or a scheduled script | Instance Scheduler on AWS (an AWS solution that starts and stops EC2 and RDS on tag-based schedules) | Instance schedules, or a scheduled script |
| Script on this site | Invoke-AzVMPowerSchedule.ps1 | Invoke-AwsEC2PowerSchedule.ps1 | Invoke-GcpVMPowerSchedule.ps1 |
The scripts on this site read a schedule tag or label, support -WhatIf, and skip VMs that belong to scale sets, Auto Scaling groups or managed instance groups, which should be scaled instead.
# Google Cloud: a weekday office-hours schedule, attached to a VM
gcloud compute resource-policies create instance-schedule sched-office-hours --region=us-east1 `
--vm-start-schedule='0 7 * * MON-FRI' --vm-stop-schedule='0 19 * * MON-FRI' `
--timezone='America/New_York'
gcloud compute instances add-resource-policies vm-notes-test-01 --zone=us-east1-b `
--resource-policies=sched-office-hours
# Azure: Stop-AzVM deallocates by default, which stops compute billing.
# -StayProvisioned keeps the VM allocated, and it keeps billing for compute.
Stop-AzVM -ResourceGroupName 'rg-notes-test' -Name 'vm-notes-test-01' -Force
Databases and other services
| Azure | AWS | Google Cloud | |
|---|---|---|---|
| Pause or stop a database | Azure SQL Database serverless pauses automatically when idle; Azure Database for PostgreSQL and MySQL flexible servers can be stopped | Amazon RDS and Aurora can be stopped for up to 7 days, then start again automatically | Cloud SQL instances can be stopped (activation policy NEVER) |
| Containers | Scale AKS user node pools to zero; stop the cluster | Scale EKS node groups down | Scale GKE node pools down; Autopilot bills for running pods |
What keeps billing when a VM is off
- Disks (OS and data) and snapshots.
- Public IP addresses: Azure Standard public IPs, every AWS public IPv4 address (US$0.005 an hour since February 2024, attached or not), and Google Cloud static external IPs that are reserved but not in use.
- Reservations and committed use discounts attached to the VM's size: the commitment is paid whether the VM runs or not. Don't schedule VMs you've committed for, and don't commit for VMs you schedule.
- Azure only: a VM that is stopped from inside the operating system is not deallocated and keeps billing for compute. Stop it from the portal, PowerShell or the CLI so it shows Stopped (deallocated).