---
title: "Container Registry Management: Tagging, Signing, and Operations"
description: "How to manage container registries including image tagging strategies, garbage collection, image signing with Cosign, pull-through caches, and authenticated pulls in Kubernetes."
url: https://agent-zone.ai/knowledge/kubernetes/container-registry-management/
section: knowledge
date: 2026-02-22
categories: ["kubernetes"]
tags: ["container-registry","image-tagging","cosign","sigstore","ecr","ghcr","harbor"]
skills: ["registry-operations","image-signing","imagepullsecrets-configuration"]
tools: ["docker","cosign","crane","skopeo","kubectl"]
levels: ["intermediate"]
word_count: 949
formats:
  json: https://agent-zone.ai/knowledge/kubernetes/container-registry-management/index.json
  html: https://agent-zone.ai/knowledge/kubernetes/container-registry-management/?format=html
  api: https://api.agent-zone.ai/api/v1/knowledge/search?q=Container+Registry+Management%3A+Tagging%2C+Signing%2C+and+Operations
---


# Container Registry Management

A container registry stores and distributes your images. Getting registry operations right -- tagging, access control, garbage collection, signing -- prevents a class of problems ranging from "which version is deployed?" to "someone pushed a compromised image."

## Registry Options

**Docker Hub** -- The default registry. Free tier has rate limits (100 pulls per 6 hours for anonymous, 200 for authenticated). Public images only on free plans.

**GitHub Container Registry (ghcr.io)** -- Tight integration with GitHub Actions. Free for public images, included storage for private repos. Authenticate with a GitHub PAT or `GITHUB_TOKEN` in Actions.

**Amazon ECR** -- Managed AWS registry. Per-region, per-account. Includes built-in image scanning (basic and enhanced via Inspector). Lifecycle policies for automated cleanup.

**Google Artifact Registry** -- Replaces GCR. Multi-format (Docker, Maven, npm, Python). Integrates with GKE workload identity for pull authentication.

**Harbor** -- Open-source, self-hosted. Supports replication, vulnerability scanning, image signing, RBAC, and audit logs. Run this when you need full control or air-gapped environments.

## Image Tagging Strategies

Tags are mutable pointers to image digests. This is a critical point: pushing a new image with the same tag overwrites the previous one.

### Avoid `:latest`

The `:latest` tag is the default when no tag is specified. It tells you nothing about what version is running. Kubernetes caches images by tag, so if you push a new `:latest` and the node already has the old one cached, it will not pull the new image unless `imagePullPolicy: Always` is set.

### Recommended: Semantic Version + Git SHA

```bash
# Tag with semantic version for humans
docker tag myapp:build myregistry.io/myapp:1.4.2

# Tag with git SHA for exact traceability
docker tag myapp:build myregistry.io/myapp:sha-a1b2c3d

# Push both
docker push myregistry.io/myapp:1.4.2
docker push myregistry.io/myapp:sha-a1b2c3d
```

In your Kubernetes manifests, reference the semantic version for readability. In your CI logs, record the SHA tag so you can trace any deployed image back to the exact commit.

A CI pipeline typically handles this:

```bash
VERSION=$(git describe --tags --always)
SHA=$(git rev-parse --short HEAD)
IMAGE="myregistry.io/myapp"

docker build -t ${IMAGE}:${VERSION} -t ${IMAGE}:${SHA} .
docker push ${IMAGE}:${VERSION}
docker push ${IMAGE}:${SHA}
```

## Authenticated Pulls in Kubernetes

Private registries require credentials. Kubernetes uses `imagePullSecrets` to authenticate.

**Create the secret:**

```bash
kubectl create secret docker-registry regcred \
  --docker-server=myregistry.io \
  --docker-username=robot-account \
  --docker-password="${REGISTRY_TOKEN}" \
  --namespace=production
```

**Reference in pod spec:**

```yaml
spec:
  imagePullSecrets:
    - name: regcred
  containers:
    - name: app
      image: myregistry.io/myapp:1.4.2
```

**Attach to a ServiceAccount so every pod in the namespace uses it:**

```bash
kubectl patch serviceaccount default -n production \
  -p '{"imagePullSecrets": [{"name": "regcred"}]}'
```

For AWS ECR, the token expires every 12 hours. Use a CronJob or a tool like `ecr-credential-helper` to refresh it automatically. For GKE with Artifact Registry, use Workload Identity to avoid managing credentials entirely.

## Image Signing with Cosign

Image signing verifies that an image was built by your CI pipeline and has not been tampered with. Cosign from the Sigstore project makes this straightforward.

**Generate a key pair (for key-based signing):**

```bash
cosign generate-key-pair
# Creates cosign.key (private) and cosign.pub (public)
```

**Sign an image after pushing:**

```bash
cosign sign --key cosign.key myregistry.io/myapp:1.4.2
```

**Verify a signature before deploying:**

```bash
cosign verify --key cosign.pub myregistry.io/myapp:1.4.2
```

**Keyless signing (recommended for CI):**

Keyless signing uses OIDC identity from your CI provider (GitHub Actions, GitLab CI) instead of a long-lived key. No key management required.

```yaml
# GitHub Actions step
- name: Sign image
  uses: sigstore/cosign-installer@v3
- run: cosign sign --yes myregistry.io/myapp:${{ github.sha }}
  env:
    COSIGN_EXPERIMENTAL: 1
```

The signature is stored in the registry alongside the image. Admission controllers (Kyverno, Connaisseur) can verify signatures at deploy time, rejecting unsigned images.

## Garbage Collection and Retention Policies

Registries accumulate images over time. Without cleanup, storage costs grow and old vulnerable images remain available.

### ECR Lifecycle Policies

```json
{
  "rules": [
    {
      "rulePriority": 1,
      "description": "Keep last 20 tagged images",
      "selection": {
        "tagStatus": "tagged",
        "countType": "imageCountMoreThan",
        "countNumber": 20
      },
      "action": { "type": "expire" }
    },
    {
      "rulePriority": 2,
      "description": "Delete untagged images older than 7 days",
      "selection": {
        "tagStatus": "untagged",
        "countType": "sinceImagePushed",
        "countUnit": "days",
        "countNumber": 7
      },
      "action": { "type": "expire" }
    }
  ]
}
```

### Harbor Garbage Collection

Harbor has a built-in garbage collector accessible via the admin UI or API. Configure a schedule and tag retention rules per project.

### GitHub Container Registry

GHCR does not have built-in lifecycle policies. Use a GitHub Action to prune old images:

```yaml
- name: Delete old container images
  uses: snok/container-retention-policy@v3
  with:
    image-names: myapp
    cut-off: 30 days ago
    keep-at-least: 10
    account: my-org
    token: ${{ secrets.GITHUB_TOKEN }}
```

## Pull-Through Caches

A pull-through cache proxies requests to an upstream registry and caches images locally. This reduces external bandwidth, avoids Docker Hub rate limits, and speeds up pulls.

Harbor supports pull-through caching natively. Configure it as a proxy cache for Docker Hub:

1. In Harbor, create a new registry endpoint pointing to `https://registry-1.docker.io`.
2. Create a project of type "Proxy Cache" linked to that endpoint.
3. Configure your Kubernetes nodes to pull from `harbor.internal/dockerhub-cache/library/nginx:1.27` instead of `nginx:1.27`.

For cloud environments, ECR has pull-through cache rules that proxy requests to Docker Hub, GitHub Container Registry, and other upstreams.

## Cost Management

Registry costs are driven by storage and data transfer. Practical measures:

- **Lifecycle policies** -- Delete images you will never deploy again. Keep the last N tagged versions.
- **Multi-stage builds** -- Smaller images mean less storage per version.
- **Single registry per region** -- Cross-region pulls incur data transfer charges.
- **Pull-through caches** -- Reduce redundant pulls from expensive upstream registries.
- **Monitor image sizes** -- Use `crane manifest --platform linux/amd64 myregistry.io/myapp:1.4.2 | jq '.config.size'` or `docker manifest inspect` to track image sizes over time.

## Key Takeaways

- Tag images with semantic versions and git SHAs. Never deploy `:latest` to production.
- Attach `imagePullSecrets` to ServiceAccounts rather than individual pod specs.
- Sign images with Cosign (keyless in CI) and verify with admission controllers.
- Set up lifecycle policies on day one. Storage costs compound silently.
- Use pull-through caches to avoid rate limits and reduce pull latency.

