AWS cost · ECR
Unused ECR images: what deleting them really frees
Every CI pipeline that builds containers leaves years of images behind it, and clearing them out is the obvious win. The trouble is working out what the cleanup is worth. The number most tools reach for - the sum of the image sizes - is wrong by five to ten times, always in the direction that makes the saving look bigger.
ECR bills $0.10/GB-month for layers, and stores each layer once per repository however many images use it. So deleting an image frees only the layers nothing else references. Fifty builds sharing a 500 MB base layer report about 25 GB between them; AWS is charging for roughly 1.5 GB.
What ECR charges, and what a cleanup returns
| What | Cost | Note |
|---|---|---|
| Stored layers | $0.10/GB-month | us-east-1, counted once per repository |
| 200 GB of layers in a repository | ~$20.00/month | what AWS actually bills |
| The same repository, image sizes summed | 5-10x higher | a number no bill will ever show |
| Deleting an image whose layers survive elsewhere | $0.00 freed | shared layers stay, and stay billed |
Why the obvious number is wrong
A container image is a stack of layers plus a small config blob. Rebuild an application on the same base image and the new build shares every layer below your own code. That is the point of the format, and the reason a push takes seconds.
The API reports each image at the size of all its layers anyway, because that is what the image weighs when you pull it. Add those figures up across a repository and you have counted the base layer once per build. AWS counted it once in total.
For a multi-architecture image the same field under-reports instead: it gives the largest child, not the total. It is wrong in both directions.
The number that is actually true
Deleting a set of images frees the layers no surviving image references, plus each deleted config blob, which is never shared. Everything else stays where it is and stays on the bill.
Getting that number means fetching each manifest and comparing layer digests between what you are deleting and what you are keeping. More work than summing a column, and the difference between a saving you can promise and one you have to apologise for.
How to find images nobody pulls
ECR records the last time each image was pulled and hands it back on the describe call, so this costs nothing to check:
# repositories in this region
aws ecr describe-repositories --query 'repositories[].repositoryName' --output text
# push and last-pull times per image, oldest push first
aws ecr describe-images --repository-name my-app \
--query 'sort_by(imageDetails,&imagePushedAt)[].{Digest:imageDigest,Pushed:imagePushedAt,LastPull:lastRecordedPullTime,Bytes:imageSizeInBytes}' \
--output table
# the manifest, where the real layer digests and sizes live
aws ecr batch-get-image --repository-name my-app \
--image-ids imageDigest=sha256:abc123... \
--query 'images[].imageManifest' --output textJudge an image on the later of its push and pull times. A rebuilt image can be pushed today and never pulled since, and reading the pull time alone would call it dead. An image with neither timestamp is not evidence of disuse - treat it as unknown.
What the cleanup costs
Rollbacks stop at the oldest image you kept. The images that look most deletable - the ones nobody has pulled in a year - are exactly the ones a rollback reaches for in the worst hour of an incident.
Anything pinned by digest breaks outright. A digest names exact bytes, so a rebuild gets a new one and the old reference can never be satisfied. Check pinned deployment manifests and container-image Lambda functions first.
# MUTATING - deletion is permanent, a digest cannot be recreated
aws ecr batch-delete-image --repository-name my-app \
--image-ids imageDigest=sha256:abc123...Deleting by hand fixes today and not next year. The durable answer is a lifecycle policy that expires images on a rule, covered in ECR storage costs.
Want the honest number - the layers a cleanup would actually free? Connect your account read-only and see what it is wasting, in real dollars.
The role can only read - Get, Describe, List, nothing else. Read the exact permissions before you deploy it, and delete the stack whenever you want.
Frequently asked questions
How much does ECR storage cost?+
$0.10 per GB-month in us-east-1, charged on the layers stored in each repository. A layer is stored once per repository no matter how many images reference it, which is why the bill is almost always smaller than the sum of your image sizes.
Why does adding up image sizes overstate my ECR bill?+
Because imageSizeInBytes is the total of all of an image's layers, shared or not. Fifty CI builds on a common 500 MB base layer report about 25 GB between them, while ECR stores and charges for roughly 1.5 GB. Summing that field overstates storage by five to ten times.
How much will deleting old images actually save?+
Only the layers that no surviving image still references, plus each deleted image's own config blob, which is never shared. If every layer of an old image also appears in the current build, deleting it frees nothing at all - the tag disappears and the bill does not move.
Are untagged images safe to delete?+
Not automatically. A multi-architecture image is a manifest list whose per-platform children show up in the API as untagged images, so deleting everything untagged can break the tagged image that points at them. Untagged means unlabelled, not unused.
How do I tell whether an image is still being pulled?+
ECR records a last-pull time per image, and it comes back on the describe call - no metrics, no cost. Use the later of the pull time and the push time: an image re-pushed last week has not been pulled since, but it is clearly current. An image with neither timestamp is not evidence of anything.
What breaks when I delete old images?+
Rollbacks past the deleted images stop working, which matters most for exactly the old images that look most deletable. Anything referencing an image by digest - a pinned deployment manifest, a Lambda function on a container image - breaks immediately, because the digest cannot be recreated by rebuilding.
Related cost breakdowns
ECR storage costs $0.10/GB - and CI builds pile it up
ECR stores container images at $0.10/GB-month, and every CI build pushes layers that never expire on their own. Without a lifecycle policy the bill climbs forever - here is how to cap it.
Orphaned EBS snapshots are quietly draining your AWS bill
EBS snapshots cost $0.05/GB-month and never delete themselves. Forgotten ones pile up into hundreds a month. Here is how to find the orphans and automate cleanup.
Why your S3 bill keeps growing - and how to stop it
Cold backups sit in S3 Standard at ~$46/month per 2 TB when Glacier Deep Archive is ~$2/month for the same files. One lifecycle rule moves them automatically.
Switch EBS gp2 to gp3 and cut 20% with no downtime
gp3 volumes cost $0.08/GB-month against gp2's $0.10 - 20% less at the same or better performance, with a live migration and no downtime. Here is the one command.