Skip to content

Container Registry Cleanup

This guide explains how to safely clean up old container images while protecting released packages.

Overview

CI/CD pipelines generate many container images over time:

  • Every commit creates a sha-* tagged image
  • Every PR creates a pr-* tagged image
  • Every release creates a version-tagged image

Without cleanup, registries accumulate thousands of images, increasing storage costs and making it harder to find relevant images.

Understanding Tags

Container registries use two distinct tagging systems that are often confused:

Image Tags (OCI/Docker)

These are the tags visible on the container image itself:

ghcr.io/org/package:sha-abc1234    # CI build
ghcr.io/org/package:v1.0.0         # Release tag
ghcr.io/org/package:latest         # Mutable pointer

Image tags are:

  • Assigned when pushing to the registry
  • Visible in docker images and registry UI
  • What you specify in docker pull

GitHub Release Tags

These are tags created via the GitHub Releases API:

eac-ext/1.0.0      # Module release
clie/2.3.4      # CLI release
clie-eac-bundle/2025.01.15  # Bundle release

Release tags are:

  • Created when publishing a GitHub Release
  • Visible in the Releases section of a repository
  • Correlate with git tags

The Connection

When you create a release, the CI pipeline typically:

  1. Creates a GitHub Release with tag module/version
  2. Pushes a container image with the same tag as an image tag

This creates a correlation between the GitHub Release and the container image. The cleanup system uses this correlation to protect released images.

Safety Model

The cleanup system uses multiple layers of protection:

┌─────────────────────────────────────────────────────────────┐
│                    PROTECTION LAYERS                        │
├─────────────────────────────────────────────────────────────┤
│  1. Image Tag Patterns (preserve)                           │
│     └─ "v*", "latest", "[0-9]*.[0-9]*.[0-9]*"               │
│                                                             │
│  2. GitHub Release API Correlation (ALWAYS ON)              │
│     └─ eac-ext/1.0.0 → protected                            │
│                                                             │
│  3. Release Bundle References (ALWAYS ON)                   │
│     └─ Versions in bundle release notes → protected         │
│                                                             │
│  4. Digest Matching                                         │
│     └─ Same digest as protected version → protected         │
│                                                             │
│  5. Minimum Age                                             │
│     └─ Created < min_age_days ago → protected               │
│                                                             │
│  6. Prune Patterns (must match to be candidate)             │
│     └─ "sha-*", "dev-*", "pr-*", "ci"                       │
└─────────────────────────────────────────────────────────────┘

Non-Configurable Safety

Some protections cannot be disabled:

Protection Why Non-Configurable
GitHub Release correlation Released packages must never be auto-deleted
Release bundle references Bundle contents must remain available

To delete a released package, use manual methods (GitHub UI or API).

Release Bundles

Release bundles aggregate multiple module releases:

# In repository.yml
- moniker: clie-eac-bundle
  release_bundle:
    headline:
      clie: clie
      eac: eac-ext
    categories:
      - name: Core Tools
        modules: [clie, eac-ext]

When a bundle is released, its release notes list the included module versions:

## Core Tools
- clie: v2.3.4
- eac-ext: v1.0.0

The cleanup system parses these notes and protects all referenced versions, even if they're older than the keep limit.

Configuration

Image Tags Section

Controls which container image tags to preserve or prune:

image_tags:
  preserve:           # NEVER delete these
    - "v*"            # Release tags like v1.0.0
    - "latest"        # Latest pointer
    - "[0-9]*.[0-9]*.[0-9]*"  # Bare semver
  prune:              # Candidates for cleanup
    - "sha-*"         # CI builds
    - "dev-*"         # Dev builds
    - "pr-*"          # PR builds
    - "ci"            # CI tag

Logic:

  • If ANY tag matches a preserve pattern → protected
  • If NO tag matches a prune pattern → protected
  • Only versions matching prune patterns are cleanup candidates

GitHub Releases Section

Configures GitHub Release API correlation:

github_releases:
  # Released packages are ALWAYS protected (non-configurable)
  tag_format: "{module}/{version}"  # How releases are tagged

The tag_format helps the system understand which image tags correspond to GitHub Releases.

General Settings

cleanup:
  enabled: true       # Master switch
  keep: 10            # Keep newest N prunable versions
  min_age_days: 7     # Don't prune versions younger than this

Cleanup Strategy

The default "keep-latest-n" strategy:

  1. Identify all prunable versions (match prune patterns, not protected)
  2. Sort by creation date (newest first)
  3. Keep the newest N versions
  4. Delete the rest
Versions (sorted newest first):
  sha-abc (2 days ago)  → KEEP (within keep limit)
  sha-def (5 days ago)  → KEEP (within keep limit)
  sha-ghi (8 days ago)  → KEEP (within keep limit)
  ...
  sha-xyz (30 days ago) → DELETE (outside keep limit)

Manual Cleanup

For released packages that must be deleted (e.g., security issues):

Using GitHub UI

  1. Go to repository → Packages
  2. Find the package
  3. Click on the version
  4. Delete (requires confirmation)

Using gh CLI

# List versions
gh api /orgs/{org}/packages/container/{package}/versions

# Delete specific version
gh api -X DELETE /orgs/{org}/packages/container/{package}/versions/{version_id}

Using Docker Registry API

# Get token
TOKEN=$(gh auth token)

# Delete manifest
curl -X DELETE \
  -H "Authorization: Bearer $TOKEN" \
  "https://ghcr.io/v2/{org}/{package}/manifests/{digest}"

Monitoring

Track cleanup effectiveness:

Metric Target Action if Exceeded
Total versions < 100 per package Increase cleanup frequency
Storage usage < quota Reduce keep count
Protected ratio < 50% Review preserve patterns

See Also


Tutorials | How-to Guides | Explanation | Reference

You are here: Explanation — understanding-oriented discussion that clarifies concepts.