GitLab Container Registry Cleanup Policies: Stop Paying for Dead Images
Every CI run pushes images you'll never use again. Without cleanup policies, the registry grows forever. Here's how to set up sane automated tag retention.
- #gitlab
- #cicd
- #container-registry
- #docker
- #cost-optimization
- #devops
Here’s a cost line nobody notices until it’s embarrassing: your GitLab container registry. Every pipeline that builds an image pushes a tag. Branch builds, MR builds, ephemeral preview builds — each one a layer set sitting in object storage forever, because by default nothing ever deletes them. I’ve inherited registries with hundreds of gigabytes of images for branches that were merged and deleted two years ago, all of it quietly billing.
The fix is cleanup policies: automated, rule-based tag retention that GitLab runs for you. It takes ten minutes to configure and pays for itself immediately. Here’s how I set it up without accidentally deleting an image something still needs.
Understand what actually consumes space
Two things matter. Tags are the names you push (registry/app:abc123). Layers are the actual content, shared across tags via content-addressable digests. Deleting a tag doesn’t immediately reclaim space — it removes the reference. The storage is reclaimed later when the garbage collector runs and finds layers no tag points to anymore.
This two-phase model trips people up: they delete a thousand tags, check storage, and see no change. The space comes back after GC. On GitLab.com this is managed; on self-managed instances, you need to actually run the registry garbage collector (gitlab-ctl registry-garbage-collect) on a schedule, or your cleanup policy deletes tags but never reclaims bytes.
The cleanup policy, explained
GitLab’s cleanup policy is a set of rules applied per project, configured under Settings → Packages and registries → Container registry cleanup policies (or via the API). The rules read confusingly because they combine an allow-list and a deny-list. Here’s the model that makes it click:
name_regex_keep— tags matching this are never deleted. Your safety net.keep_n— always keep at least this many of the most recent matching tags.older_than— only consider deleting tags older than this.name_regex— of the remaining candidates, delete those matching this pattern.
So a tag is deleted only if it matches name_regex, is older than older_than, isn’t in the most-recent keep_n, and doesn’t match name_regex_keep. Four conditions, all must hold. That layering is what keeps it safe.
A policy I actually use
Here’s a configuration via the API that keeps releases forever, keeps the last few of everything, and aggressively prunes old ephemeral builds:
curl --request PUT \
--header "PRIVATE-TOKEN: $TOKEN" \
--header "Content-Type: application/json" \
"https://gitlab.com/api/v4/projects/$PROJECT_ID" \
--data '{
"container_expiration_policy_attributes": {
"enabled": true,
"cadence": "1d",
"keep_n": 10,
"older_than": "14d",
"name_regex": ".*",
"name_regex_keep": "^(v\\d+\\.\\d+\\.\\d+|latest|main|release-.*)$"
}
}'
What this does:
- Runs daily (
cadence: 1d). - Considers every tag (
name_regex: ".*"). - But never touches semver tags,
latest,main, orrelease-*branches (name_regex_keep). - Always keeps the 10 newest matching tags (
keep_n). - Only deletes things older than 14 days (
older_than).
The net effect: feature-branch and MR images vanish two weeks after they go stale, while anything that looks like a real release lives forever.
Tag for cleanup-friendliness from the start
A cleanup policy is only as good as your tagging discipline. If every image is just :latest, you have nothing to discriminate on. Tag with intent in CI so the policy has signal to work with:
build:
script:
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
build-release:
rules:
- if: $CI_COMMIT_TAG
script:
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG
Now release builds carry a semver tag your name_regex_keep protects, and commit builds carry a SHA your policy will eventually prune. The cleanup policy and your tagging convention are two halves of one design.
Test before you trust it
Deleting images is destructive and there’s no undo. Before enabling a policy on a busy project:
- Start with a generous
older_than(30 days, say) and a highkeep_n, watch one run, then tighten. Don’t go aggressive on day one. - Verify your
name_regex_keepactually matches your release tags. A regex typo here is how you delete the image production is running. Test the pattern against your real tag list first. - Remember it only deletes tags, not the in-use image. If a Kubernetes deployment references a tag by digest, deleting the tag doesn’t pull the running container — but you lose the ability to roll forward to it. Protect tags your deployments reference.
- On self-managed, schedule garbage collection. Otherwise you’re deleting references and reclaiming nothing.
Where to go from here
Container registry growth is a silent, compounding cost that a ten-minute cleanup policy eliminates. Tag images with intent in CI, protect anything that looks like a release with name_regex_keep, start conservative and tighten, and on self-managed instances actually run garbage collection. Set it once and the registry stops being a landfill.
For more on keeping GitLab CI lean and cheap, see our GitLab CI/CD guides. And when reviewing changes to your build-and-push jobs or tagging scheme, our AI code review assistant helps catch the tagging mistakes that make cleanup policies useless.
Cleanup and garbage-collection behavior differ between GitLab.com and self-managed. Test any policy on a low-stakes project before applying it broadly.
Download the Free 500-Prompt DevOps AI Toolkit
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.