Post

Your ECR storage bill is not the problem. $0.10/GB-month, so 2 TB is ~$200/mo. The expensive part rides on stale images: Inspector rescanning all of them on every new CVE, replication copying every CI tag to every region. An agent that cleans it without deleting a thing 🧵

Contrarian bit first: the agent never calls batch-delete-image. Its only write path is a PR to the Terraform that owns the repos, adding a lifecycle policy. ECR does the expiring, on rules a human reviewed. An LLM with delete rights on a registry = Friday ImagePullBackOff.

The policy everyone copies from the docs (expire untagged after 7 days) fixes the SMALLEST bucket. 140-repo account: - untagged 61k images / 38% of size - never-pulled 48k / 41% - stale 9.4k / 15% - active 2.1k / 5% 84 of 140 repos had no policy. 12 had the untagged-only one.

Why never-pulled dominates: CI tags every commit SHA. Monorepo, 40 services, 30 merges/day = ~1,200 images/day, each a 400 MB tag pulled once by the job that built it, or never. No age rule touches them while they're under your threshold. You need a COUNT rule on the SHA pattern.

Collector is read-only boto3: DescribeRepositories, DescribeImages, GetLifecyclePolicy, DescribeRegistry, DescribePullThroughCacheRules. The field that makes it work: lastRecordedPullTime (per image since 2022, ~24h granularity). 90-day idle test absorbs the lag. Not a week.

0 reposts0 likes

Two numbers that lie. Summing imageSizeInBytes is not the bill: shared layers are stored once. Rank with it, bill from Cost Explorer TimedStorage-ByteHrs. Multi-arch: a tagged index points at platform manifests listed as UNTAGGED. 'Expire untagged' kills the arm64 half of a tag.

The in-use set is what makes this safe. Resolve EVERYTHING to digests, tags move: - running pods: containerStatuses[].imageID across every kube context - ReplicaSets retained by Deployments (revisionHistoryLimit default 10 = 10 rollback targets) - Helm history (history-max 10)

...plus Argo CD app history (Git revision, render manifests, extract images), Lambda container functions (Code.ResolvedImageUri is already a digest), every ACTIVE ECS task definition revision, digests named in Kyverno / signed-image allowlists. Union = protected, any age.

The LLM does ONE job: read 200 sample tags per repo and propose tagPatternList globs (sha-*, v*, main-*). Regex gets team tagging conventions wrong in surprising ways; a model doesn't, if it only fills a fixed schema. Count rule keeps max(revisionHistoryLimit, helm history) + 5.