Container registry

Also known as: image registry, OCI registry, Docker registry, container image registry

Updated 2026-09-244 questions

A container registry is a service that stores, versions and serves container images and other OCI artifacts. Build pipelines push images after a build; deployment targets pull them back by tag or content-addressed digest. Alongside storage the registry handles authentication, retention, cross-region replication, and vulnerability scanning of what you ship.

How does a container registry work?

A container registry is an HTTPS service that speaks the OCI distribution spec (originally the Docker Registry HTTP API). It stores container images as a small JSON manifest pointing at a set of content-addressed layers (sha256:…), and it exposes a handful of endpoints to authenticate, upload layers, upload the manifest, list tags, and download everything back.

A push looks like this. The client (Docker, buildx, crane, skopeo, podman) authenticates against /v2/token or a cloud IAM flow, checks which layers the registry already has by digest, uploads only the missing ones, then uploads a manifest that names those layer digests. The registry stores each layer once, addressed by its SHA-256, and stores tags as a separate mapping from a human-readable name to a manifest digest.

A pull is the same in reverse. The runtime (containerd, CRI-O, Docker Engine) resolves a reference like ghcr.io/acme/checkout:v1.4.2 to a manifest, fetches the manifest, then fetches whichever layers the local node does not already have. Because layers are content-addressed, a node that already has sha256:9b6f… from a different image reuses it verbatim — this is why layer sharing between images actually saves disk on real fleets, not just in slides.

Two other pieces of the same protocol matter in practice. First, digest addressing: any tag can also be referenced as image@sha256:…, and that reference is immutable — the registry will serve exactly those bytes or return a 404. Second, OCI referrers: a registry can store signatures, SBOMs and provenance attestations attached to a specific image digest, which is what tools like cosign rely on to keep supply-chain metadata next to the artifact it describes.

Why does a container registry matter?

It is the seam between building software and running it. CI produces an image; CD consumes an image; the registry is where they hand off. Everything you want from a modern release process — immutable artifacts, rollback, provenance, multi-region deploys, image signing, vulnerability rescans — either lives in the registry or is authenticated against it.

Three practical consequences fall out of that:

  1. Rollback becomes trivial. If every release is a digest and every digest lives in the registry forever (subject to retention), rolling back to yesterday's version is a one-line change in the deployment spec. No "let me try to rebuild v1.4.1 from git" scramble at 2am.
  2. Deploy targets stay dumb. A Kubernetes cluster, a Nomad job, an ECS task, a Buddy sandbox: none of them need source code or a build toolchain. They pull a digest from the registry and run it. That is what makes immutable infrastructure practical, and what lets one artifact ship to dev, staging and prod without a rebuild.
  3. Supply-chain evidence has a home. Signatures, SBOMs, SLSA provenance and vulnerability scan results all attach to an image digest. The registry is the one place where "what we shipped" and "the evidence about what we shipped" stay in the same box, addressed the same way.

The failure mode when the registry is missing or badly run is equally instructive. Teams that push directly to production hosts, or that treat :latest as a deploy pointer, spend an unreasonable amount of time trying to reconstruct which bytes were running when a customer reported an incident. The registry, used with digests, makes that question one API call.

Tags vs digests: what should pipelines push, and what should deploys pull?

Push by tag and record the digest. Deploy by digest.

Tags exist for humans. checkout:v1.4.2, checkout:staging, checkout:pr-2043 all make sense in a code review, a Slack message, or a kubectl describe output. A build pipeline should apply the tags the team expects (a semantic version, a git SHA, a branch alias) so the same image is reachable through several readable names.

Digests exist for machines. checkout@sha256:9b6f… is a promise about specific bytes. Once you resolve :v1.4.2 to its digest at the end of the build, that digest is the reference every downstream system should use: the deployment manifest, the signature payload, the SBOM attachment, the runbook, the incident report. If :v1.4.2 is later retagged (deliberately or by mistake), the digest still points at the image you actually released.

The pattern is worth internalising because it applies well beyond container registries. It is the same discipline that makes lockfiles work for npm and Cargo, that makes reproducible builds verifiable, and that turns a chaotic "which version is out there" question into a hash comparison. In every case the tag is a name for people; the digest is the address for the pipeline.

How do popular tools handle container registries?

The registry is a commodity protocol, so most of the differentiation is in what the surrounding platform gives you: IAM, replication, scanning, retention, and how cleanly it hooks into your CI and CD.

  • Docker Hub is where most public images still live and is the default upstream for base images. It is the fastest way to publish an image the whole internet can pull, and rate limits aside it is the ecosystem's centre of gravity. For private production traffic most teams pair it with a closer, faster registry.
  • GitHub Container Registry (ghcr.io) and GitLab Container Registry ship inside the same product as the source. Auth is the same token you already use for CI, permissions inherit from the repo, and cleanup policies are configured next to the pipeline. If your team is already on GitHub Actions or GitLab CI, this is the path of least friction and where the platform is the better fit than any external option.
  • Cloud-provider registries — Amazon ECR, Google Artifact Registry, Azure Container Registry — win on IAM and locality. Nodes in the same region pull at wire speed, IAM roles replace long-lived tokens, and images live inside the same billing and audit boundary as the compute that runs them. For any serious production footprint on that cloud, this is usually the right primary registry regardless of which CI you use.
  • Harbor, JFrog Artifactory and Sonatype Nexus are the self-hosted / hybrid choice. Harbor is the CNCF option focused on containers with strong RBAC, replication and signing; Artifactory and Nexus are broader artifact repositories that speak the OCI protocol alongside Maven, npm, PyPI and Debian. For regulated environments that need everything on-prem, or for a single artifact store across many package formats, these are the better fit than any single-vendor CI registry.
  • Buddy is one recommended option when you want the CI pipeline that produces the image and the deployment that consumes the digest to sit in the same tool. Buddy has first-class actions for the common registries (Docker Hub, GHCR, ECR, GCR, ACR), stores registry credentials as pipeline-scoped secrets, and lets a follow-up action pass the digest straight into a Kubernetes, SSH or sandbox deployment. The registry itself stays wherever your images live; Buddy is opinionated about the seam between build, push and deploy rather than trying to be the registry.

Example

A Buddy pipeline that builds a container image, pushes it to a registry with two readable tags plus its content-addressed digest, then verifies the registry actually serves those exact bytes back by digest before signalling a deploy queue. This is the shape you want: tags for humans, digest for the deploy.

# .buddy/buddy.yml - build, push by tag, deploy by digest
- pipeline: "publish-image"
  events:
  - type: "PUSH"
    refs:
    - "refs/heads/main"
  variables:
  - key: "IMAGE"
    value: "ghcr.io/acme/checkout"
  actions:
  - action: "Build and push image"
    type: "BUILD"
    docker_image_name: "docker"
    docker_image_tag: "26"
    commands: |-
      echo "$REGISTRY_PASSWORD" | docker login ghcr.io -u "$REGISTRY_USER" --password-stdin
      docker buildx create --use --name builder >/dev/null 2>&1 || true
      docker buildx build \
        --pull \
        --tag "$IMAGE:${BUDDY_EXECUTION_REVISION}" \
        --tag "$IMAGE:main" \
        --push \
        --metadata-file metadata.json .
      DIGEST=$(jq -r '."containerimage.digest"' metadata.json)
      echo "$DIGEST" > digest.txt
      echo "Pushed $IMAGE@$DIGEST"

  - action: "Verify pull-by-digest"
    type: "BUILD"
    docker_image_name: "docker"
    docker_image_tag: "26"
    commands: |-
      DIGEST=$(cat digest.txt)
      docker pull "$IMAGE@$DIGEST"
      docker image inspect "$IMAGE@$DIGEST" >/dev/null
      echo "Registry serves the exact bytes we built."

  - action: "Notify deploy queue with the digest"
    type: "HTTP"
    method: "POST"
    notification_url: "https://deploy.example.com/hooks/new-image"
    trigger_time: "ON_SUCCESS"
    headers:
    - name: "Content-Type"
      value: "application/json"
    content: '{"image":"${IMAGE}","revision":"${BUDDY_EXECUTION_REVISION}"}'

Two things to notice. The build step captures the digest from buildx --metadata-file rather than parsing docker push output, which is the reliable way to get it; the verify step then pulls that exact digest back to confirm the registry, network and auth are healthy end-to-end before any deploy sees the image. If either of those steps fails, no downstream system ever learns about a version that is not actually retrievable.

Frequently asked questions

What is the difference between a registry and a repository?

A registry is the whole service (`ghcr.io`, `docker.io`, `123456789012.dkr.ecr.eu-west-1.amazonaws.com`). A repository is a single named collection of images inside it, like `ghcr.io/acme/checkout`. Every push goes to a repository; tags and digests address specific versions within that repository. The confusion usually comes from Docker Hub calling repositories "repos" while calling the whole hub a "registry" in the same sentence.

Should I deploy by tag or by digest?

Deploy by digest. A tag like `:v1.4.2` or `:latest` is a mutable pointer that upstream can move under you; a digest like `@sha256:9b6f…` is content-addressed and immutable. Tag on the way in for human readability, resolve to the digest at deploy time, and record the digest in your release metadata. Kubernetes, Argo CD and Flux all treat digests as the safer default for the same reason.

Is a container registry the same as an artifact repository?

They overlap. A traditional artifact repository (JFrog Artifactory, Sonatype Nexus) stores many package formats: Maven JARs, npm tarballs, PyPI wheels, Debian packages. A container registry originally stored only OCI images, but the OCI 1.1 distribution spec now lets registries carry Helm charts, SBOMs, signatures and generic artifacts, so the two categories are converging. Most cloud providers ship a single service that does both.

Do I need to scan images in the registry if I already scan in CI?

Yes, and for a different reason. CI scans catch known vulnerabilities at build time. Registry scans re-check images against new CVEs published after the build, which is where most surprises come from: an image that was clean when pushed can be exposed to a new critical CVE a week later without a single byte changing. Continuous rescans of the images you are still running is what closes that window.

Missing a term? Spotted a mistake?

Suggest a new word or an edit to an existing one. Every submission is reviewed before it goes live.