# distribution/distribution: the reference OCI registry implementation

> The Go codebase behind Docker Hub, GitHub Container Registry and Harbor's storage layer. What the registry binary does, how to build it, and where it stops being the right choice.

**distribution/distribution** — The toolkit to pack, ship, store, and deliver container content

- Repository: https://github.com/distribution/distribution
- Website: https://distribution.github.io/distribution
- Stars: 10,634 · Forks: 2,794
- Language: Go
- License: Apache-2.0
- Published: 2026-09-21 · Updated: 2026-09-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/distribution-distribution

## What distribution/distribution actually is, and who needs it

This repository is not a product you install once and forget. It is the Open Source Registry implementation for storing and distributing container images and other content using the OCI Distribution Specification, and the README is explicit that the goal is to provide a base for building a large scale registry solution or running a simple private registry. The distinction matters: the repository ships a working registry binary, but its primary consumers are registry operators. The README names Docker Hub, GitHub Container Registry, GitLab Container Registry, DigitalOcean Container Registry, the CNCF Harbor project and VMware Harbor Registry as users of the code as a core library.

So the audience splits in two. The first group runs the registry binary directly, usually because they want a private registry inside a network boundary, or because they want to control where image layers physically live. The second group imports the Go packages and builds something larger on top. If you are in the first group, the practical question is which storage driver you can operate and how you will authenticate clients. If you are in the second, note the README's own warning that the library interfaces are unstable, which means you should expect to track upstream changes rather than pin once and walk away.

## How the registry handles a push and a pull

The mechanism is HTTP, and the README is direct about it: clients implement against the OCI specification and communicate with the registry using HTTP. There is no proprietary protocol and no agent to install on the client side. A client resolves a name to a repository, checks whether a blob already exists by digest, uploads the bytes if it does not, then commits a manifest that references those digests. Pulls reverse the flow: fetch the manifest, read the layer digests out of it, fetch each blob.

The repository layout reflects this split. Top-level files such as blobs.go, manifests.go, tags.go and registry.go define the domain objects and the service interface, while the registry/ directory holds the implementation and internal/ holds supporting code. Storage drivers live under registry/storage/driver, and the Makefile's coverage target explicitly filters that path out, which tells you the drivers are treated as a separate concern from the core logic. The go.mod file shows the storage backends the project carries dependencies for: cloud.google.com/go/storage for GCS, the Azure azblob and azidentity SDKs, and aws/aws-sdk-go for S3. Redis appears twice, as a cache and as a tracing exporter. OpenTelemetry packages are present for tracing, and docker/go-metrics for metrics.

That dependency list is the honest description of the architecture. The registry is a stateless HTTP front end over a pluggable blob store, with optional Redis caching, optional event notifications through docker/go-events, and optional tracing. Everything optional is a subsystem you can leave disabled.

## Building the registry from the Makefile

The repository provides a Dockerfile that builds the registry binary from source, and the Makefile defines an image target with IMAGE_REPO defaulting to distribution/distribution and IMAGE_TAG defaulting to latest. The Dockerfile builds ./cmd/registry into /usr/bin/registry, so the container's entry point is the registry binary itself. The Makefile lists the project binaries as registry, digest and registry-api-descriptor-template, and the Dockerfile's build stage invokes xx-go build against ./cmd/registry. The project's full documentation, including configuration, is published at distribution.github.io/distribution, and configuration examples live in the configuration/ directory.

The Makefile's default goal is help, so a bare make prints the available targets rather than building anything:

```bash
make help
```

The image target builds the container image using IMAGE_REPO and IMAGE_TAG, both of which can be overridden on the command line:

```bash
make image
```

To build the binaries locally instead, the Makefile's binaries target produces bin/registry, bin/digest and bin/registry-api-descriptor-template:

```bash
make binaries
```

The Makefile also defines build, test, test-race, test-full, integration, validate, lint and vendor targets. The registry binary needs a configuration file before it will serve traffic; the configuration reference is published in the project's documentation at distribution.github.io/distribution, alongside the examples in configuration/. Once the registry is listening, a client pushes against the host and port you configured, and a pull from the same repository name should return the image you just pushed. The README does not document a rollback or migration procedure for storage backends, so treat the driver choice as something to get right before you have data.

## The client library is deprecated, and that is the real constraint

The README states plainly that the client implementation in this repository is in use by Docker but is deprecated in favour of the implementation in containerd and will not support new features. That is the single most consequential limitation for anyone planning to build tooling here. If your plan involves writing a Go program that pushes and pulls images, the project itself is steering you toward containerd's remotes/docker package rather than its own client.

The second limitation is scope. This repository implements the distribution specification and provides libraries. It does not ship a web interface, a user management system, image signing policy, or a vulnerability scanner. Those exist in products that build on this code, which is why the README lists Harbor among its consumers. A team that adopts distribution/distribution expecting a turnkey artifact platform will spend its first month writing the parts that were never in scope.

The third is the storage driver. The project supports several backends, but it does not make them equivalent. A filesystem driver on a single node has different failure characteristics from an object store, and the repository does not document a migration path between them. Choosing the driver is an architectural decision, not a configuration detail.

Finally, the README warns that the library interfaces are unstable. Anyone importing these packages should expect breaking changes across minor versions rather than assuming the API is frozen.

## distribution/distribution compared with Harbor

Harbor is the natural comparison because it is not a competitor so much as a layer above. The README lists the CNCF Harbor Project among the operators that use this codebase as a core library, which means Harbor embeds a registry rather than replacing it. The difference in approach is what each project considers its job.

This repository's job is the storage and delivery protocol. It answers questions about digests, manifests, blob uploads and storage backends. Harbor's job is the platform around that: projects and role-based access control, a web UI, replication between registries, image scanning integration, and retention policies. If you need those, you are not choosing between two registries; you are choosing whether to assemble them yourself.

The trade-off is operational surface. Running the registry binary means one process, one config file and one storage backend. Running Harbor means several services and their dependencies. For a small team that only needs authenticated push and pull inside a private network, the extra components in Harbor are cost without benefit. For an organisation with multiple teams, audit requirements and a scanning mandate, the registry binary alone leaves all of that as work you own.

## Release cadence, licence and what upgrading costs

The repository is not archived, and the last push was on 2026-09-21. Recent releases are v3.0.0 on 2025-04-03, v3.1.0 on 2026-04-06 and v3.1.1 on 2026-05-01. The gap between the v3.0.0 major release and the v3.1.x line is roughly a year, so the practical upgrade rhythm is measured in months rather than weeks. The go.mod file declares go 1.26.0, and the Dockerfile pins GO_VERSION to 1.26.8 and ALPINE_VERSION to 3.23, so a build from source requires a matching toolchain.

Upgrade cost concentrates in three places. The registry version is stamped into the binary at build time through -X flags on version.version, version.revision and version.mainpkg, so a self-built binary reports whatever git describe produced. The configuration file carries a version field, and a major release is where config schema changes land. The library interfaces are unstable per the README, so code that imports the packages carries more upgrade risk than a deployment that only runs the binary.

On licensing, the README states the codebase is released under the Apache 2.0 license, while README.md and files in the docs folder are licensed under Creative Commons Attribution 4.0 International. That split is worth knowing if you intend to reuse documentation text in your own product. Apache 2.0 includes an express patent grant and requires attribution and notice retention; if you redistribute a modified registry, those obligations travel with it. This is a description of what the repository states, not legal advice, and organisations with strict licence review processes should route the question to whoever handles that.

## Conclusion

Adopt distribution/distribution when you need a self-hosted OCI registry that speaks the standard pull and push API and you accept operating its storage backend yourself; the registry binary plus a config file and a storage driver is the whole deployment. Do not adopt it if you want a web UI, RBAC, vulnerability scanning or garbage-collection scheduling out of the box, because none of those are in this repository and Harbor or another distribution built on top of it is the better fit. Before committing, read the configuration reference for the storage driver you intend to use, confirm your chosen backend's consistency behaviour under concurrent pushes, and decide whether you need the notification and Redis-backed cache subsystems at all, since every enabled subsystem is another moving part you will have to monitor.

## FAQ

### What is distribution/distribution used for?

It is the Open Source Registry implementation for storing and distributing container images and other content using the OCI Distribution Specification. The README describes it as a base for building a large scale registry solution or running a simple private registry, and lists Docker Hub, GitHub Container Registry, GitLab Container Registry, DigitalOcean Container Registry, the CNCF Harbor Project and VMware Harbor Registry among its users.

### How do I build and install distribution/distribution?

The repository ships a Dockerfile that builds ./cmd/registry into /usr/bin/registry, and the Makefile provides an image target with IMAGE_REPO defaulting to distribution/distribution and a binaries target that produces bin/registry, bin/digest and bin/registry-api-descriptor-template. Configuration examples live in the configuration/ directory, and the full documentation is published at distribution.github.io/distribution.

### Is the client library in distribution/distribution still supported?

No. The README states the client implementation in this repository is in use by Docker but is deprecated in favour of the implementation in containerd and will not support new features.

### What license does distribution/distribution use?

The README states the codebase is released under the Apache 2.0 license, while README.md and files in the docs folder are licensed under the Creative Commons Attribution 4.0 International License.

### Are the Go library interfaces in distribution/distribution stable?

No. The README's component table marks the library interfaces as unstable, so code that imports the packages should expect changes.

## Sources

- [distribution/distribution on GitHub](https://github.com/distribution/distribution)
- [License: Apache-2.0](https://github.com/distribution/distribution/blob/main/LICENSE)
- [Project website](https://distribution.github.io/distribution)
- [README](https://github.com/distribution/distribution/blob/main/README.md)
- [Releases](https://github.com/distribution/distribution/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/distribution-distribution
