AI Agents · Containers

Container Registry Best Practices

Container Registry Best Practices is the work that turns a deploy into a system. The deployment is one moment; the system is the next 18 months of uptime, incidents, and.

John Kihiu12 min read

A container registry is infrastructure, not just image storage, and it gets treated as an afterthought more often than any other piece of the deploy pipeline. Tag hygiene, access control, and retention policy all decay quietly until a rollback fails because the image you need was garbage-collected, or a compromised CI token pushes to a registry with no scanning and no audit trail. None of the fixes are exotic; they just need to be decided on purpose instead of by default.

Stop using mutable tags for deploys

latest is the most common source of "it worked yesterday" incidents. It is a mutable pointer — pushing a new image under the same tag silently replaces what latest resolves to, which means a rollback to "the previous latest" is not actually possible once a new push has happened. Deploy by immutable reference: either a semantic version tag that's never reused, or better, the image digest (registry/app@sha256:...), which is content-addressed and can never point to different content. CI should resolve and record the digest at build time and pass that digest through to the deploy step, not the tag.

YAML · GITHUB ACTIONS — TAG AND PUSH BY DIGEST
- name: Build and push
  id: push
  uses: docker/build-push-action@v6
  with:
    push: true
    tags: registry.example.com/app:${{ github.sha }}

- name: Record digest for deploy
  run: echo "IMAGE_DIGEST=${{ steps.push.outputs.digest }}" >> "$GITHUB_ENV"

Access control and least-privilege push rights

Anyone or anything that can push to a production registry can, intentionally or via a compromised credential, ship malicious code straight to your clusters. Scope push access to CI service accounts only, never to individual developer credentials, and scope those service accounts to the specific repository paths they need — a service that builds one app shouldn't have push rights to every namespace in the registry. Pull access can usually be broader, but even that should be scoped per-environment if the registry holds anything sensitive.

A registry credential with broad push access is a supply-chain single point of failure

If that credential leaks — in a log, a misconfigured CI variable, a compromised dependency — the blast radius is every image behind it. Rotate registry credentials on a schedule, use short-lived tokens (OIDC federation to the registry, where supported) instead of long-lived static keys, and alert on pushes from unexpected sources.

Vulnerability scanning at push time

Most managed registries (ECR, GAR, ACR, Docker Hub, GitHub Container Registry, Harbor self-hosted) support scanning images on push using Trivy, Grype, or a vendor-native scanner. Wire the scan result into the pipeline as a gate for at least critical and high severity findings on the base OS layer — not necessarily blocking every build on every finding, since some are unfixable or false positives, but blocking silently shipping a known-critical CVE with an available fix.

Retention and garbage collection

Registries fill up, and the instinct to "just keep everything" runs into both storage cost and, more importantly, into the case where you actually need an old image for a rollback and it's gone because someone ran an aggressive cleanup script without understanding the retention needs. A sane default: keep every tagged release image indefinitely (they're small relative to the value of being able to roll back), and garbage-collect untagged/dangling layers and CI-branch images (like `pr-1234`) after a short window — a week or two is usually enough.

Untagged does not mean unused

An image can be untagged in the registry's tag list but still be the exact digest a running deployment references. Registry garbage collection should check against what's actually deployed, or at minimum you need a long enough retention window that a stuck rollback doesn't hit a wall.

Signing and provenance

Image signing (Sigstore/Cosign is the common open-source path) lets a cluster admission controller verify that an image was built by your CI pipeline and hasn't been tampered with or swapped for something pushed by an unauthorized party. This is worth adopting once you have the basics above solid — access control, immutable references, and scanning — since signing without those in place mostly signs a pipeline that could already be compromised at an earlier step.

PracticePrevents
Deploy by digest, not mutable tagSilent image swaps, broken rollbacks
Scoped, short-lived push credentialsSupply-chain compromise via leaked credentials
Scan-at-push with a severity gateKnown-critical CVEs reaching production
Retention policy that checks deployed digestsGarbage-collecting an image you still need
Image signing (Cosign/Sigstore)Unauthorized or tampered images passing admission

Wrapping up

Registry hygiene is unglamorous work that mostly pays off during an incident: the rollback that actually works because you deployed by digest, the compromised token that couldn't reach production because it was scoped to one repo, the CVE that got caught before it shipped. Fix the mutable-tag problem first — it's the cheapest change with the most direct effect on whether a rollback is actually possible when you need one.

John Kihiu
Acumatica ERP Developer · Laravel Engineer

Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.