Updated Jun 30, 2026

What a Registry Actually Is

Think about what a build produces. You run a docker build and get an image. You run mvn package and get a jar. You run npm publish and you've got a tarball. These are artifacts — the compiled, packaged, ready-to-ship outputs of your work. They are not source code; source lives in Git. Artifacts are what source becomes, and they need a different kind of home, because they are large, binary, versioned, and pulled by machines you'll never log into.

That home is an artifact registry: a server that stores artifacts, organizes them by name and version, and serves them over HTTP to anyone (or any pipeline) with permission to pull. Git is for the recipe; the registry is for the cake.

Two flavors, one idea

You'll meet registries in two shapes, and it helps to see they're the same idea wearing different clothes:

  • Container registries store OCI images. Docker Hub is the public default; GHCR (GitHub Container Registry, ghcr.io) lives next to your repos; every cloud has one (ECR, GCR/Artifact Registry, ACR).
  • Package/multi-format registries store language packages — npm, Maven, PyPI, NuGet, Go modules, and often container images too. Nexus (Sonatype) and Artifactory (JFrog) are the two big self-hosted, multi-format players. One server, many package types.
  your build  ─push─▶   REGISTRY   ─pull─▶  CI / teammates / production
   (image,              (stores +
    jar, npm)            versions +
                         serves)

What just happened: the registry sits in the middle of everything that produces and everything that consumes artifacts. That central position is exactly why getting it right matters — a flaky or untrustworthy registry breaks builds and deploys everywhere downstream at once.

The part that governs everything: tags vs digests

Here is the single most important idea in the whole guide, and the source of most registry pain when it's misunderstood.

When you push a container image, the registry stores the actual bytes under a digest — a content hash that looks like sha256:9b2a.... The digest is the content. Change one byte of the image and you get a completely different digest. A digest is permanent and immutable: sha256:9b2a... will forever mean exactly those bytes, or it won't exist at all.

A tag — like 1.4.0 or latest — is a separate, human-friendly label that points at a digest. And a tag is a pointer that can be moved.

$ docker pull nginx:1.27.0
1.27.0: Pulling from library/nginx
Digest: sha256:67682bda769fae1ccf5183192b8daf37b64cae99c6c3302650f6f8bf5f0f95df
Status: Downloaded newer image for nginx:1.27.0

What just happened: you asked for the tag 1.27.0, and the registry resolved it to a digest and handed you those exact bytes. The tag was the address; the digest was the content. To pin truly immutable, you can pull by digest directly — docker pull nginx@sha256:67682b... — and you will get those bytes no matter what anyone does to the tag later.

The mental model that saves you: a digest is a fact (this content, forever). A tag is an opinion (right now, I think 1.27.0 means this). Opinions can change. Facts cannot.

Why immutability is the whole game

Once you internalize "tag is a movable pointer, digest is fixed content," the rest of registry behavior stops surprising you.

Most version tags should behave as if immutable: once myapp:1.4.0 is built and pushed, it should mean that exact build forever, so that the image your CI tested is byte-for-byte the image production runs. If someone can re-push a different image to 1.4.0, you've lost the thread that connects "what we tested" to "what we shipped."

The famous exception is latest. It is a tag like any other, with zero special meaning to the registry — it's a conventional name people move to point at "the most recent build." That convenience is also a footgun, which we'll dissect in phase 3.

# Two ways to reference the same image:
$ docker pull myapp:1.4.0                    # by tag — convenient, movable
$ docker pull myapp@sha256:9b2a3c...         # by digest — pinned, immutable

What just happened: the first line trusts that the 1.4.0 pointer still aims where you expect. The second line bypasses tags entirely and demands specific content. Production deploys that care about reproducibility increasingly pin by digest for exactly this reason.

Why not keep artifacts in Git or a file share?

People try. Here's why it falls apart, so you understand what a registry is actually buying you:

  • Versioning that machines understand. A registry exposes a standard API (the OCI distribution spec for images, ecosystem-specific protocols for npm/Maven/PyPI) so docker pull, npm install, and mvn all work against it without custom glue.
  • Deduplication by content. Images are built in layers; packages share dependencies. A registry stores each unique layer/blob once and references it from many artifacts, so ten images sharing a base layer don't cost ten copies.
  • Access control and provenance. Who can push? Who can pull? When was this published, and by whom? A registry answers these; a shared folder does not.

A Git repo is built for diffable text and small files with full history. A 900 MB image with binary layers is everything Git is bad at.

For builders

If you're standing up infrastructure, your first registry decision is usually "public-hosted vs self-hosted." Docker Hub and GHCR are managed and zero-ops for images. Nexus and Artifactory are servers you run, earning their keep when you need one place for many package formats, fine-grained access control, and the proxying and retention features we'll cover later. There's no universally right answer — there's the one that fits your team's formats, scale, and tolerance for running infrastructure.

[
  {
    "q": "In a container registry, what is the relationship between a tag and a digest?",
    "choices": [
      "A tag and a digest are two names for the exact same immutable thing",
      "A digest is a content hash that permanently identifies specific bytes; a tag is a movable label that points at a digest",
      "A tag is the content hash; a digest is the human-friendly label",
      "Digests are only used by Nexus, not Docker Hub"
    ],
    "answer": 1,
    "explain": "The digest is the content (a permanent hash of the bytes). The tag is a separate pointer that can be moved to point at a different digest."
  },
  {
    "q": "Why store images and packages in a registry instead of a Git repo or shared folder?",
    "choices": [
      "Registries are always cheaper than Git",
      "Git cannot store any binary files at all",
      "Registries speak standard pull APIs, deduplicate by content (shared layers/blobs), and enforce access control and provenance",
      "Registries automatically rewrite your source code"
    ],
    "answer": 2,
    "explain": "Registries are purpose-built for versioned binary artifacts: a standard pull API, content-addressed dedup, and access control — all things Git and file shares handle poorly."
  },
  {
    "q": "What does pulling by digest (image@sha256:...) guarantee that pulling by tag does not?",
    "choices": [
      "A faster download",
      "You get exactly those bytes regardless of whether anyone later moves the tag",
      "The image is automatically scanned for vulnerabilities",
      "The registry deletes all other tags"
    ],
    "answer": 1,
    "explain": "A digest is content-addressed and immutable, so it always resolves to the same bytes. A tag is a movable pointer that someone could repoint."
  }
]

← Overview | Phase 2: Pushing, Pulling, and Private Packages

Check your understanding 3 questions

1. In a container registry, what is the relationship between a tag and a digest?

2. Why store images and packages in a registry instead of a Git repo or shared folder?

3. What does pulling by digest (image@sha256:...) guarantee that pulling by tag does not?

Was this page helpful?