← All episodes

Episode 1 · October 12, 2026

ECR Images With No Lifecycle Policy: Storage Costs and Safe Expiry Rules

When a new container image is pushed to Amazon ECR, the image it replaces loses its tag but stays in the repository, and ECR storage is billed at $0.10 per GB-month. This short episode explains why untagged does not mean unused: orchestrators often run images by digest, and the untagged image may be what is running in production or what a rollback needs. It covers checking for a missing policy with GetLifecyclePolicy, then confirming real image counts and push dates with describe-images. It also explains why a sinceImagePushed window counts from the push date and does not protect rollbacks, and how to keep the last N releases with a tagged rule and a tagPatternList, with any catch-all rule at the highest priority number. It notes that the lifecycle preview shows what would expire, not whether expiring it is safe, and recommends checking the digests of running tasks before adding an untagged expiry rule. To find old and untagged ECR images in your own AWS account, the free, source-available CloudWise scanner (pip install cloudcostwise) runs read-only on your machine and sends nothing to CloudWise.

ECR Images With No Lifecycle Policy: Storage Costs and Safe Expiry Rules

Read the related post

Transcript

Auto-generated from the audio. The hosts are AI-generated voices; mistakes in the transcript are possible.

You know, when you replace a worn-out part in a machine, you don't just, like, toss the old gear into the bottom of the casing and leave it there. Right, yeah. I mean, it would eventually jam the mechanism, right? You'd spot it immediately. Exactly.

It's highly visible. But when your team pushes a new container image to Amazon ECR, the old image just loses its release tag. It doesn't disappear. It just sits there, untagged, acting exactly like that discarded gear. And it is definitely not free.

So welcome to this deep dive. Today we are looking through some engineering notes and AWS docs to uncover this massive, hidden billing trap in Amazon ECR. Yeah, and the billing mechanics on this are totally transparent, actually. But they still somehow catch teams completely off guard. So ECR storage, it costs 10 cents per GB month, which means, you know, if you are storing 100 gigs of untagged images, that costs you $10 a month.

Or, just as a yearly illustration, that is $120 for every 100 GB. Which really adds up fast. Yeah. But, I mean, why does this waste survive so reliably? It accumulates silently because it is, well, completely invisible on your per-service AWS bill.

Oh, absolutely. It triggers zero CloudWatch alarms, and usually there is just no clear organizational owner between the app and infrastructure teams to actually clean it up. Yeah, no one wants to touch it. Right. But wait, if an image loses its tag, isn't it just, you know, digital exhaust?

I mean, my first instinct is to assume it is junk and just blindly wipe it with some automated script. Oh, hold on. That is a really dangerous assumption. You should never, ever consider untagged images as exhaust or doing nothing, because untagged does not mean unused. Wait, really?

How does that work? Well, container orchestrators frequently pull images directly by their digest. Oh, the digest. So meaning the specific cryptographic fingerprint of that exact build. Right, exactly.

So the untagged image that your deployment pipeline just replaced, that is very often the exact image currently running in production right now. Or it is the exact image you are going to need if you suddenly have to trigger an emergency rollback. Oh, wow. Okay, that completely changes the cleanup strategy. So if they're invisible on the bill, an engineer's first instinct is probably to just check the repository's lifecycle policies.

Which makes sense, yeah. But if you run get lifecycle policy and the API tells you no policy is found, that proves nothing, right? Like it could just be an empty repository or one pruned manually by your CI pipeline. You've got it, yeah. The lack of a policy doesn't actually prove images are piling up.

The next necessary step is to actually use the describe-images command. Right, describe-images. Yeah, that is the only way to look at the actual image counts and their push dates. Okay, so let's say you do that and you see them piling up. How do you fix it safely?

Like if I want a clean house, is setting a simple age limit just the easiest fix? Well, here's the trap with that. AWS has a count type called since-image-pushed, but it counts days from the image's original push date, not from when it lost its tag. Oh. Wait, let me puzzle that out.

So if you only release code, say, every 30 days, but you set a 14-day expiry window. Then the replaced image is already 30 days old. It expires the second it loses its tag, and ECR will vanish it within 24 hours. Oh, no. So you just destroyed your rollback path.

Exactly. Yikes. You never want to present a since-image-pushed window as rollback protection. Yikes. Okay, so instead of a time limit, you have to basically tell ECR to count backward.

You configure a rule that says keep the last five tag releases, no matter how old they are. Yes, exactly. You protect rollbacks by keeping the last n tagged releases. In the policy, you would use tag status tagged to find your release tags with a tag pattern list, and then use image count more than. Got it.

And ECR evaluates all these lifecycle rules together, right? So if you add a catch-all rule at the end, like tag status any, to clean up the remaining untagged images, it has to be assigned the highest rule priority number so it evaluates last. Yes, that is crucial. Each image is expired by at most one rule, and they are applied by priority. Also keep in mind that if you run a lifecycle policy preview, it only shows what a rule would expire.

Right. It doesn't tell you if it is actually safe to do so. Exactly. And once applied, expiry happens within 24 hours. Okay.

Now, if you want to spot this pattern across your accounts without writing custom scripts, you can actually use a free source-available local scanner. Just run pip install cloudcostwise. Right, yeah. It covers 20 AWS services, runs strictly read-only on your own machine, and simply flags these old and untagged ECR images. And it sends absolutely nothing to CloudWise, right?

Exactly. It will never automatically fix, delete, or rollback anything. But regardless of the tool you use, here is the absolute golden rule. Always check the digests of your running tasks. You have to check what is actually running before you add an untagged expiry rule.

Verify before you delete. Don't just assume that old gear isn't still turning a critical part of your machine. Which I guess leaves us with a final question for you to ponder. What else in your architecture is currently running on a tiny invisible dependency like a digest thread, completely vulnerable to an aggressive cleanup script?

Find this waste in your own account, read-only, on your own machine: pip install cloudcostwise