Detect Idle VMs and Unattached Disks on AWS, GCP, Azure
Pull the utilisation evidence yourself, decide what idle means for each workload, then confirm before you stop or delete anything.
“Idle” is a judgement, not a metric. A bastion host can sit at 1% CPU and still be essential; a batch worker can look busy while doing nothing useful. What the clouds give you is evidence: utilisation history, attachment state and their own recommenders. This guide collects that evidence on each provider so the decision is made on data.
On AWS: two weeks of CPU and network for an instance
CloudWatch keeps 1-hour data points for 455 days, 5-minute points for 63 days and 1-minute points for 15 days, so a 14-day window at a 1-hour period is always available and stays well under the 1,440 data points one call can return.
aws cloudwatch get-metric-statistics \ --namespace AWS/EC2 --metric-name CPUUtilization \ --dimensions Name=InstanceId,Value=i-0abc123def4567890 \ --start-time 2026-09-10T00:00:00Z --end-time 2026-09-24T00:00:00Z \ --period 3600 --statistics Maximum Average \ --query "sort_by(Datapoints,&Timestamp)[].[Timestamp,Average,Maximum]" --output textUse Maximum, not only Average: an instance that averages 2% but peaks at 90% every night runs a job. Repeat with --metric-name NetworkOut to see whether it serves anything. Set your own thresholds per workload type and write them down, so the next review uses the same bar.
On AWS: volumes attached to nothing
An EBS volume in the available state is not attached to any instance but still bills for its provisioned size.
aws ec2 describe-volumes --filters Name=status,Values=available \ --query "Volumes[].[VolumeId,Size,VolumeType,CreateTime]" --output tableOld CreateTime values with no recent owner are the usual cleanup list. Snapshot before deleting if anyone might want the data (aws ec2 create-snapshot --volume-id vol-... --description "pre-delete").
On Google Cloud: the idle VM recommender
Compute Engine generates idle VM recommendations per zone at no charge. You need the recommender.computeInstanceIdleResourceRecommendations.list permission on the project.
gcloud recommender recommendations list \ --project=my-project --location=us-central1-c \ --recommender=google.compute.instance.IdleResourceRecommender \ --format=yamlAn empty response means no idle VMs were found in that zone. Loop over the zones you use, since the location is per zone.
On Azure: Advisor cost recommendations
az advisor recommendation list --category Cost --output tableAdd --refresh to ask Advisor to regenerate recommendations before listing. Read each recommendation’s impacted resource and suggested action before acting on it.
Confirm before you act
For each candidate, answer three questions: who owns it (tags, creator in the audit log), what depends on it (security group members, load balancer targets), and what happens on stop versus delete. An EC2 stop keeps EBS data but drops instance store data and an auto-assigned public IP; a delete is permanent. Start with a stop where the resource allows it, wait a working week, then delete if nobody asked for it back.
With ZopNight
ZopNight pulls CloudWatch, Cloud Monitoring and Azure Monitor metrics into one store and raises idle, orphan and rightsizing recommendations with the monitoring values that triggered each one attached as evidence. If the built-in definitions do not match your idea of idle, a user-defined watch policy sets your own signals and thresholds, for example average CPU under a chosen percentage over N days, and its results appear on a separate tab. Before acting, the blast radius view lists connected resources; see checking blast radius before you remediate. Background reading: idle resource detection.