Skip to main content
emnode / learn
Cost

Deregister unused AMIs and their snapshots

AMIs are free; the EBS snapshots backing them aren't — clean up years of forgotten golden images.

13 min·10 sections·AWS

Last reviewed

Unused AMIs: the basics

What does "unused" mean and why does it cost anything?

An Amazon Machine Image (AMI) is a template for launching EC2 instances. The image itself — the manifest, the kernel pointer, the block-device mapping — costs nothing to keep around. What does cost money is what sits behind it: one or more EBS snapshots, billed per GB-month of stored data, for every volume the AMI was built from.

A multi-volume golden image — root disk plus a few data disks — can easily have three or four snapshots attached to it. At roughly $0.05 per GB-month, a 100 GB AMI quietly bills $5 a month, a 500 GB multi-volume AMI bills $25-30 a month, and a fleet that has accumulated hundreds of forgotten AMIs over five years of CI builds is paying real money for images that nobody has launched in years.

The wastage signal is simple: the AMI hasn't been used to launch an instance for N days, where N is usually 90 or 180. The wastage checker doesn't care about the per-AMI dollar amount — individually they look like rounding errors. It cares about the compound bill across the long tail. Real findings flag AMIs created 1,400 to 1,800 days ago that nobody can name a purpose for.

In this lesson you'll learn how to identify genuinely unused AMIs, why deregistering an image is only half the cleanup job, where the "orphaned snapshot" trap hides, and how to spot the AMIs you absolutely must not touch — the ones still referenced by Launch Templates, Auto Scaling Groups, CloudFormation stacks, AWS Backup vaults, or Image Builder pipelines. You'll see the exact CLI calls to inventory, classify, and safely clean up, plus the policy you need so this doesn't happen again.

Fun fact

The orphaned snapshot bill

Deregistering an AMI does not delete the EBS snapshots it points to. AWS made that deliberate — they don't want you accidentally losing your backups when you tidy up an image. The consequence is a classic FinOps trap: a team runs aws ec2 deregister-image in a loop, congratulates itself for the cleanup, and the next month's bill is identical. The snapshots are still sitting in aws ec2 describe-snapshots, owner-id is still you, and the storage charge keeps ticking. Most multi-year AWS accounts have orphaned snapshots that have outlived the AMI that created them by literal years.

AMI cleanup in action

Nina has just inherited an AWS account from a team that shipped a product five years ago. The wastage report flags 312 unused AMIs in one region alone, with creation dates ranging from 400 to 1,780 days old. Total backing snapshot storage: 11.4 TB.

She doesn't trust the "unused" label on its own — the previous team built a habit of keeping a single "DR golden image" per quarter for compliance. Some of these AMIs are genuinely abandoned CI artifacts; others might be load-bearing. She needs to separate the two before she deletes anything.

Her plan is three passes: enumerate, filter by reference (Launch Templates, ASGs, CloudFormation, Image Builder, Backup), then deregister + delete-snapshot for anything that survives both filters and is older than 180 days with no Keep tag.

First, list every AMI owned by the account along with creation date and the snapshot IDs it references.

$ aws ec2 describe-images --owners self --query 'Images[].{ID:ImageId,Created:CreationDate,Name:Name,Snaps:BlockDeviceMappings[].Ebs.SnapshotId}' --output table
---------------------------------------------------------------------------------------------
| DescribeImages |
+------------------------+---------------------------+--------------------------------+
| Created | ID | Name |
+------------------------+---------------------------+--------------------------------+
| 2021-06-14T03:11:09Z | ami-0a1b2c3d4e5f6a7b8 | ci-build-prod-2021-06-14 |
| 2022-02-22T01:04:51Z | ami-09f8e7d6c5b4a3210 | app-server-golden-2022-02 |
| 2024-11-08T18:42:17Z | ami-0123456789abcdef0 | packer-bake-prod-2024-11-08 |
| 2026-04-30T07:00:00Z | ami-0fedcba9876543210 | app-server-golden-current |
+------------------------+---------------------------+--------------------------------+
# 312 rows total. The 2021 and 2022 rows are over 1,000 days old.

Account inventory of self-owned AMIs with creation dates.

Before deleting anything, check which AMIs are still referenced by something that could launch them tomorrow — Launch Templates, ASGs, and any active Image Builder pipeline.

$ aws ec2 describe-launch-template-versions --launch-template-id lt-0abc123def456 --query 'LaunchTemplateVersions[].LaunchTemplateData.ImageId' --output text
ami-0fedcba9876543210
ami-0123456789abcdef0
ami-09f8e7d6c5b4a3210
# That last one is 1,178 days old — still wired into the prod launch template.
# Do NOT deregister. Investigate why an old AMI is still referenced before touching it.

Cross-referencing the inventory against active Launch Template versions.

Deregistration under the hooddeep dive

An AMI is a thin metadata record: a manifest pointing at one snapshot per block device, a kernel/ramdisk pair (for paravirtual images), architecture, virtualization type, and ENA/SR-IOV flags. aws ec2 deregister-image deletes that manifest. After deregistration the image-id is no longer launchable and disappears from describe-images — but the underlying EBS snapshots remain owned by your account and continue to bill at the standard snapshot rate.

EBS snapshot pricing is per GB-month of changed data, deduplicated against earlier snapshots of the same volume. The first snapshot of a 100 GB volume is roughly 100 GB billable; subsequent snapshots only bill the delta. That delta-dedup is the reason naively counting snapshot sizes overestimates the bill, and the reason deleting a single snapshot in a chain can briefly increase the cost of the next one as AWS rebalances the storage references behind the scenes.

Once an AMI is deregistered, AWS gives you no built-in way to associate the snapshots back to the image. The link is gone. The only forensic trail is the snapshot's Description field, which usually contains text like Created by CreateImage(ami-0a1b2c3d4e5f6a7b8) — that's how cleanup scripts identify orphans. If a previous engineer deregistered images without saving the snapshot IDs first, you can still recover them by scanning descriptions and matching the embedded ami-id.

# Two-step cleanup: deregister the AMI, then delete each snapshot it referenced.
IMAGE_ID=ami-0a1b2c3d4e5f6a7b8

# 1. Capture the snapshot IDs BEFORE deregistering — afterwards the link is gone.
SNAPSHOTS=$(aws ec2 describe-images --image-ids $IMAGE_ID \
  --query 'Images[].BlockDeviceMappings[].Ebs.SnapshotId' --output text)

# 2. Deregister the AMI.
aws ec2 deregister-image --image-id $IMAGE_ID

# 3. Delete each backing snapshot.
for snap in $SNAPSHOTS; do
  aws ec2 delete-snapshot --snapshot-id $snap
done

What is the impact of carrying years of unused AMIs?

The direct bill is small per AMI and large in aggregate. A single 100 GB AMI costs ~$5/month in snapshot storage; a 500 GB multi-volume golden image costs $25-30/month. An account with 300 unused AMIs averaging 200 GB across two snapshots each is silently burning roughly $6,000 a month — $72k a year — on images nobody can name the purpose of.

The second-order impact is operational. Every AMI in the account is a candidate to be launched by an outdated Launch Template version or a stale CloudFormation stack drift. Some of those AMIs predate critical security patches. A junior engineer running aws ec2 run-instances --image-id <old-ami> will happily spin up a six-year-old, unpatched Ubuntu image — and the AMI being present in the account is what makes that possible.

On the FinOps reporting side, AMI/snapshot spend almost always lands in an "unallocated EBS" or "misc storage" line on the cost report. Teams look at it once a year, shrug, and move on. Cleaned up properly, that line becomes legible — and the EBS storage row in your cost-by-service breakdown starts matching what's actually running in production.

On the regulatory side, retaining AMIs forever without a documented retention policy fails most audit frameworks (SOC 2, ISO 27001, HIPAA). "We keep every AMI we've ever built" is not a backup strategy — it's the absence of one. A real policy says "DR golden image every quarter, retained for 13 months, tagged Keep=DR," and everything else is cleaned up on a 90-day rolling window.

How do you clean up AMIs safely?

AMI cleanup is a four-step loop. Skip the reference check and you take down a recovery flow; skip the snapshot delete and you've achieved nothing.

1. Inventory and age every AMI

Run describe-images --owners self per region and capture creation date, name, tags, and the full list of backing snapshot IDs. Snapshots are owned by the account, not the AMI, so capturing IDs before you deregister anything is the difference between a clean cleanup and a forensic recovery exercise later. Persist the inventory — a flat JSON in S3 is enough.

2. Filter out in-use and intentionally-retained images

Cross-reference the inventory against every consumer that can launch an AMI: Launch Templates (all versions, not just $Latest), Auto Scaling Groups, CloudFormation stacks (AWS::EC2::Instance, AWS::EC2::LaunchTemplate resources), AWS Backup recovery points, Image Builder pipelines, and Systems Manager Parameter Store entries (/aws/service/ami-* patterns or your own custom params). Then exclude anything tagged Keep=DR or younger than your retention window. What's left is the safe-to-delete set.

3. Deregister, then delete the snapshots

Always two operations, always in that order. Deregister releases the AMI metadata; delete-snapshot reclaims the storage. A script that runs only the first step gives you the false satisfaction of a smaller AMI count and an unchanged bill. Run the loop in batches with a dry-run flag, log every deletion to CloudTrail (it does this automatically), and keep a 30-day quarantine period in a non-prod account before deleting if you're nervous.

4. Codify the retention policy

AMI/snapshot lifecycle is the kind of thing that drifts the instant nobody is looking. Use Amazon Data Lifecycle Manager (DLM) or AWS Backup lifecycle rules to auto-deregister and delete AMIs older than N days, with tag-based exclusions for Keep=DR and Compliance=*. Have CI pipelines that build AMIs (Packer, Image Builder) tag every artifact with BuildId, ExpiresAfter, and Pipeline so the cleanup script never has to guess what something is for.

# Safe deletion script: age > 180 days, no Keep tag, not referenced by any Launch Template.
CUTOFF=$(date -u -d '180 days ago' +%FT%TZ)

# Pull AMIs in use by any Launch Template version.
IN_USE=$(aws ec2 describe-launch-templates --query 'LaunchTemplates[].LaunchTemplateId' --output text \
  | xargs -n1 -I{} aws ec2 describe-launch-template-versions --launch-template-id {} \
    --query 'LaunchTemplateVersions[].LaunchTemplateData.ImageId' --output text \
  | sort -u)

# Candidate set: self-owned, old enough, not tagged Keep, not referenced.
aws ec2 describe-images --owners self \
  --query "Images[?CreationDate<'$CUTOFF' && !not_null(Tags[?Key=='Keep'])].{ID:ImageId,Snaps:BlockDeviceMappings[].Ebs.SnapshotId}" \
  --output json > candidates.json

# Review candidates.json against $IN_USE manually before running the loop below.
# jq -r '.[] | select(.ID as $i | $in_use | index($i) | not) | .ID + " " + (.Snaps | join(" "))' candidates.json \
#   | while read ami snaps; do
#       aws ec2 deregister-image --image-id $ami
#       for s in $snaps; do aws ec2 delete-snapshot --snapshot-id $s; done
#     done

Quick quiz

Question 1 of 5

You've identified 200 AMIs older than 180 days, with no Keep tag, that don't appear in any Launch Template, ASG, or CloudFormation stack. What's the right cleanup sequence?

You've completed Deregister unused AMIs and their snapshots. You now know why the AMI itself is free, where the real storage bill hides, and the two-operation cleanup that actually reclaims the spend. The next time the wastage report flags a wall of AMIs created 1,500 days ago, you'll have a four-step loop — inventory, filter, deregister + delete-snapshot, codify the policy — ready to run without breaking a recovery flow.

Back to the library