Schedule dev and staging instances to cut their bill by 64%
Running non-production only on weekdays from 08:00 to 20:00 cuts its compute bill by 64%. How to find those instances and schedule them on AWS and Google Cloud.
Contents
In short
- A week has 168 hours; weekdays 08:00–20:00 are 60 of them.
- Tags tell you what’s non-production: env=dev, staging, test, QA, sandbox.
- Stopped instances keep their disks, so the saving is on compute only.
Development, test and staging environments usually run around the clock, even though people use them during working hours. Every night and every weekend, they bill the full rate for nobody. Running them only when people work is the largest saving available on non-production compute, and it needs no change to the software.
The rule this guide follows: an instance tagged as non-production (dev, development, test, testing, staging, stage, QA or sandbox) that runs 24/7 should run weekdays from 08:00 to 20:00 only.
The math
A week has 168 hours. Weekdays from 08:00 to 20:00 are 12 hours a day, five days a week:
| Hours a week | Share of the week | |
|---|---|---|
| Running 24/7 | 168 | 100% |
| Weekdays 08:00–20:00 | 60 | 36% |
| Saved | 108 | 64% |
So scheduled instances bill 64% less for compute. Twelve hours covers a long working day across a couple of time zones. Narrow it if your team is in one place.
Find non-production instances
Tags are how you tell environments apart. List running instances whose env tag marks them as non-production:
aws ec2 describe-instances \
--filters Name=instance-state-name,Values=running \
Name=tag:env,Values=dev,development,test,testing,staging,stage,qa,sandbox \
--query 'Reservations[].Instances[].[InstanceId, InstanceType, Tags[?Key==`Name`]|[0].Value]' \
--output table
Tag keys and values are both case-sensitive in EC2 filters. The query above matches env=qa but not env=QA or Env=Staging. If teams spell them differently, add each spelling to the filter, then agree on one.
Instances with no env tag at all are worth a list of their own. They’re the ones nobody can schedule safely until someone says what they are.
What a schedule saves, and what it doesn’t
A stopped instance stops billing for compute, but not for everything around it (AWS docs):
- Compute and data transfer: not billed while stopped.
- EBS volumes: billed every hour, stopped or not.
- Elastic IPs: billed while the instance is stopped.
- Each start: bills at least one minute.
The 64% applies to the instance hours, not the whole environment. Storage and addresses keep their cost, so delete what the environment doesn’t need: unattached volumes and unused public IPs are the usual leftovers.
Schedule it on AWS
There are two good ways, depending on how many instances you have.
Instance Scheduler on AWS is an AWS solution that stops and starts instances by tag, across accounts and Regions. It’s the better fit for many environments.
EventBridge Scheduler works for a handful of instances, with no extra infrastructure. Two schedules call the EC2 API directly: one stops at 20:00, one starts at 08:00, on weekdays:
aws scheduler create-schedule --name stop-staging \
--schedule-expression "cron(0 20 ? * MON-FRI *)" \
--schedule-expression-timezone "America/New_York" \
--flexible-time-window Mode=OFF \
--target '{
"Arn": "arn:aws:scheduler:::aws-sdk:ec2:stopInstances",
"RoleArn": "arn:aws:iam::123456789012:role/scheduler-ec2",
"Input": "{\"InstanceIds\":[\"i-0123456789abcdef0\"]}"}'
aws scheduler create-schedule --name start-staging \
--schedule-expression "cron(0 8 ? * MON-FRI *)" \
--schedule-expression-timezone "America/New_York" \
--flexible-time-window Mode=OFF \
--target '{
"Arn": "arn:aws:scheduler:::aws-sdk:ec2:startInstances",
"RoleArn": "arn:aws:iam::123456789012:role/scheduler-ec2",
"Input": "{\"InstanceIds\":[\"i-0123456789abcdef0\"]}"}'
The role needs ec2:StopInstances and ec2:StartInstances on those instances, and must trust scheduler.amazonaws.com.
Auto Scaling groups schedule themselves. Set capacity to zero at night and back in the morning:
aws autoscaling put-scheduled-update-group-action --auto-scaling-group-name staging-asg \
--scheduled-action-name night --recurrence "0 20 * * 1-5" --time-zone "America/New_York" \
--min-size 0 --max-size 0 --desired-capacity 0
aws autoscaling put-scheduled-update-group-action --auto-scaling-group-name staging-asg \
--scheduled-action-name morning --recurrence "0 8 * * 1-5" --time-zone "America/New_York" \
--min-size 1 --max-size 4 --desired-capacity 2
Use the group’s real daytime sizes in the morning action.
Schedule it on Google Cloud
Compute Engine has schedules built in, as resource policies. Find the VMs by label:
gcloud compute instances list \
--filter="labels.env:(dev development test testing staging stage qa sandbox) AND status=RUNNING" \
--format="table(name, zone.basename(), machineType.basename(), labels.env)"
Create a weekday schedule in the VMs’ Region, then attach it:
gcloud compute resource-policies create instance-schedule weekday-hours \
--region=us-central1 \
--vm-start-schedule="0 8 * * 1-5" \
--vm-stop-schedule="0 20 * * 1-5" \
--timezone="America/New_York"
gcloud compute instances add-resource-policies staging-api \
--zone=us-central1-a --resource-policies=weekday-hours
The Compute Engine service agent needs permission to start and stop the VMs; Google’s docs on instance schedules list the role to grant. As on AWS, persistent disks keep billing while the VMs are stopped.
Make it stick
Schedules break when people need the environment at 21:00 and turn the scheduler off for good. Give them a way around it instead:
- An opt-out tag. Something like
schedule=offthat the scheduler skips, with an owner and a date. - A way to start it on demand. A documented command or a chat shortcut that runs the start schedule’s call.
- Tags at launch. Make
envrequired in launch templates and infrastructure code, so new environments are scheduled from day one.
Omni FinOps flags non-production instances that run around the clock on every refresh. Schedule before you commit, too: scheduled instances lower your steady compute floor, and a Compute Savings Plan should be sized after that drop, not before.
Mateo Vargas writes Omni FinOps’ cost guides, from the same rules the product runs on every refresh. Prices are list prices in us-east-1 or us-central1 as of October 11, 2026; check your region’s pricing page.