Skip to main content
INS // Insights

Unattached EBS Volume Cost Cleanup at Org Scale

Updated September 2026 · 5 min read

Unattached EBS volume cost cleanup is the org-wide hunt for volumes in available — disks that survived the instance. AWS bills provisioned GB and IOPS whether or not anything is mounted. A gp3 volume at the common ~$0.08/GB-month list (confirm on the EBS pricing page) looks small until fifty “temporary” 500 GB volumes are sitting in three Regions. That is $2,000 / month for data nobody is reading.

The second half of the job is DeleteOnTermination defaults. Root volumes usually die with the instance. Data volumes attached after launch default to preserve. Autoscaling, Terraform prevent_destroy mistakes, and Kubernetes PersistentVolume retain policies are how available becomes a standing line item.

Unattached EBS Volume Cost Cleanup: Available Means Billable

State available means “not attached.” It does not mean “free.” Preserving volumes on termination is explicit: preserved volumes continue to incur charges until you delete them (or snapshot then delete). Snapshots are a different, cheaper, line — do not confuse snapshot storage with volume storage.

We inventory every account and Region. Console muscle memory is one Region. The waste is in the Region nobody opens.

# Org-wide available volumes — run per account via a read-only role
for r in $(aws ec2 describe-regions --query 'Regions[].RegionName' --output text); do
  aws ec2 describe-volumes --region "$r" \
    --filters Name=status,Values=available \
    --query 'Volumes[].[VolumeId,Size,VolumeType,CreateTime,AvailabilityZone,Tags]' \
    --output table
done

Config aggregator (or a Security Hub custom insight) is what we leave behind so this is not a quarterly SSH-into-the-CLI ritual. A Config rule on ec2-volume-in-use-check plus a weekly Athena query on the Config snapshot is enough. Tag orphan-candidate on first sight; delete only after the age gate and the snapshot policy.

Scorecard columns we put in front of a CFO:

Account Region Vol type GB Est. $/mo Age (days) Last attach (CloudTrail) Keep?
prod us-west-2 gp3 500 $40 87 none snapshot + delete
sandbox us-east-1 io2 200 (IOPS-heavy) 14 last week wait

io2/io1 orphans are the expensive ones. Provisioned IOPS bill even detached. A leftover 10,000-IOPS volume is a different conversation than an 8 GB gp3 root that escaped a spot instance.

DeleteOnTermination Is the Recurrence Control

Cleanup without defaults is a one-time refund. We set the attribute on launch templates, Auto Scaling launch templates, and Terraform root_block_device / ebs_block_device so the next terminate does not refill available.

AWS defaults (summarized from the preserve-volumes guide):

Volume When attached Typical default
Root At launch Delete
Root After launch Preserve
Data At launch via console Preserve
Data After launch Preserve

That table is why “we always terminate instances” is not a storage strategy. Console-attached data volumes are the usual leak.

# Show DeleteOnTermination on a running instance
aws ec2 describe-instance-attribute --instance-id i-0123456789abcdef0 \
  --attribute blockDeviceMapping \
  --query 'BlockDeviceMappings[].{dev:DeviceName,del:Ebs.DeleteOnTermination,vol:Ebs.VolumeId}'

# Flip a data volume that should die with the instance
aws ec2 modify-instance-attribute --instance-id i-0123456789abcdef0 \
  --block-device-mappings \
  'DeviceName=/dev/sdf,Ebs={VolumeId=vol-0abc,DeleteOnTermination=true}'
resource "aws_instance" "app" {
  ami           = var.ami
  instance_type = var.instance_type

  root_block_device {
    volume_size           = 30
    volume_type           = "gp3"
    delete_on_termination = true
    encrypted             = true
  }

  ebs_block_device {
    device_name           = "/dev/sdf"
    volume_size           = 100
    volume_type           = "gp3"
    delete_on_termination = true
    encrypted             = true
  }
}

Keep false only for volumes with a named restore story — and then the story must include a snapshot lifecycle. “We might need it” without a snapshot is how 400 GB sits available for a year.

Kubernetes needs its own default: reclaimPolicy: Delete on StorageClasses that are not the stateful exception, and a periodic list of Released PVs. EKS node termination with DeleteOnTermination=false on extra data disks is a second source of orphans we see next to the Kubernetes containerization work.

Snapshot, Then Delete — Not Delete-First

We never mass-delete on the first pass. The runbook:

  1. Inventory available + CloudTrail DetachVolume / TerminateInstances / DeleteVolume for 90 days.
  2. Snapshot if the volume is > N GB or tagged backup=true or last-attached to a prod account.
  3. Tag orphan-delete-after=<date>.
  4. Delete after the age gate (typically 14 days in sandbox, 30 in prod) unless someone claimed it.
  5. Recycle Bin retention on delete-volume so a bad delete is a restore, not a lawsuit.

Snapshots are ~$0.05/GB-month of changed data — usually far cheaper than keeping the live volume. Incremental snapshots of an idle volume still cost less than the volume itself.

This cleanup sits with VPC endpoint vs NAT and OpenSearch right-sizing on the same scorecard: waste that does not scale with traffic. It is AWS cloud infrastructure delivery — org-wide, tagged, reversible.

Need the available-volume hunt run across the Organization? 907-841-8407 or contact@rutagon.com.

Ask for unattached EBS volume cost cleanup →

Frequently Asked Questions

Does stopping an instance create unattached EBS charges?

No. Stopped instances keep volumes attached (in-use). You still pay EBS, but they are not orphans. available is the detached state after terminate, explicit detach, or a failed attach. Stopping is a compute save; it is not a volume save.

If DeleteOnTermination is true, why do we still find orphans?

Because something attached a volume after launch (console, SSM, an operator), or a launch template left data disks at the console default (preserve), or a Kubernetes PV used Retain. The attribute is per attachment, not a global account setting. We audit running instances and templates, not just the pile of available volumes.

Should every volume delete on termination?

No. Data disks that are the system of record should snapshot on a schedule and may preserve on terminate on purpose. The failure mode is preserve-by-default with no snapshot and no owner tag. Named exceptions stay; anonymous preserve is what we remove.

Do snapshots replace the need to delete the volume?

Yes for the storage bill of the volume. After a snapshot, delete the available volume or you pay both. Snapshot lifecycle (expire old snaps) is a follow-on task so this cleanup does not just move spend to EBS:SnapshotUsage.

How do we find volumes in accounts nobody logs into?

Assume a read-only Organizations role and loop Regions, or use Config aggregators. EC2 Global View helps one account. It does not replace an org-wide query. The first cleanup we run almost always finds a forgotten Region.