MCPcopy Index your code
hub / github.com/aceeric/ociregistry

github.com/aceeric/ociregistry @1.13.0

Chat with this repo
repository ↗ · DeepWiki ↗ · release 1.13.0 ↗ · + Follow
463 symbols 1,479 edges 63 files 380 documented · 82% 13 cross-repo links
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

logo

Version: 1.13.0 Unit tests Go Report Card Go Vuln Check Artifact HUB coverage

Ociregistry is a pull-only, pull-through, caching OCI Distribution server. That means:

  1. It exclusively provides pull capability. It does this by implementing a subset of the OCI Distribution Spec.
  2. It provides caching pull-through capability to multiple upstream registries: internal, air-gapped, or public; supporting the following types of access: anonymous, basic auth, HTTP, HTTPS (secure & insecure), one-way TLS, and mTLS. In other words, one running instance of this server can simultaneously pull from docker.io, quay.io, registry.k8s.io, ghcr.io, your air-gapped registries, in-house corporate mirrors, etc.

Goals

The goal of the project is to build a performant, simple, reliable edge OCI Distribution server for Kubernetes. One of the overriding goals was simplicity: only one binary is needed to run the server, and all state is persisted as simple files on the file system under one subdirectory. These files can easily be used to inspect the server cache using well-known tools like grep, find, jq and so on.

And because of this design, the entire image store can be tarred up, copied to another location, and un-tarred. And then simply starting the server in that remote location with the --image-file arg pointing to the copied directory will serve exactly the same image cache.

Detailed Documentation

Full documentation is available on the GitHub Pages site. As you can also see in the badges above, a Helm chart is available on Artifacthub for running the registry as a Kubernetes workload.

Quick Start

Git clone the project. Then you have two ways to run the server. 1) Run the container, or 2) build the server and run the binary. These are now explained:

Run the container

The hack directory has a script to run the container. You supply three args: the server version, the port to serve on, and the image cache path:

hack/test-local-image 1.12.6 8080 /tmp/images

Tail the logs: docker logs ociregistry -f.

From Source

Build the Server:

make server

This command compiles the server and creates a binary bin/ociregistry relative to the project root.

Run the Server:

bin/ociregistry --log-level info --image-path /tmp/images serve --port 8080

The --image-path option is the image cache path. By default the server listens on 8080 but we'll specify it explicitly with the --port option.

Server startup logs

Depending on whether you run the container, or the local binary, you will see slight differences in the startup banner:

----------------------------------------------------------------------
OCI Registry: pull-only, pull-through, caching OCI Distribution Server
Version: v1.12.6, build date: 2026-03-07T23:34:41.00Z
Started: 2026-03-08 00:32:00.287358959 +0000 UTC (port 8080)
Running as (uid:gid) 1000:1000
Process id: 1
Tls: none
Command line: bin/ociregistry --log-level info --image-path /tmp/images serve --port 8080
----------------------------------------------------------------------
INFO[0000] server is running                            

Pull through the server

Assuming you have Docker (or Podman, or Crane, or your other favorite registry client), you can pull images through the Ociregistry server. The example below uses the in-path image url form that both Docker and Ociregistry understand. Run this command in another console window:

docker pull localhost:8080/registry.k8s.io/kube-scheduler:v1.29.1

Result

If you used docker you should see this output:

v1.29.1: Pulling from registry.k8s.io/kube-scheduler
aba5379b9c6d: Pull complete 
e5dbef90bae3: Pull complete 
fbe9343cb4af: Pull complete 
fcb6f6d2c998: Pull complete 
e8c73c638ae9: Pull complete 
1e3d9b7d1452: Pull complete 
4aa0ea1413d3: Pull complete 
65efb1cabba4: Pull complete 
13547472c521: Pull complete 
53f492e4d27a: Pull complete 
6523efc24f16: Pull complete 
Digest: sha256:a4afe5bf0eefa56aebe9b754cdcce26c88bebfa89cb12ca73808ba1d701189d7
Status: Downloaded newer image for localhost:8080/registry.k8s.io/kube-scheduler:v1.29.1
localhost:8080/registry.k8s.io/kube-scheduler:v1.29.1

Observe the Ociregistry logs showing how the server handled the pull:

INFO[0199] get /v2/                                     
INFO[0199] echo server GET:/v2/ status=200 latency=73.392µs host=localhost:8080 ip=127.0.0.1 
INFO[0199] pulling manifest from upstream: "registry.k8s.io/kube-scheduler:v1.29.1" 
INFO[0199] echo server HEAD:/v2/registry.k8s.io/kube-scheduler/manifests/v1.29.1 status=200 latency=507.437429ms host=localhost:8080 ip=127.0.0.1 
INFO[0199] serving manifest from cache: "registry.k8s.io/kube-scheduler@sha256:a4afe5bf0eefa56aebe9b754cdcce26c88bebfa89cb12ca73808ba1d701189d7" 
INFO[0199] echo server GET:/v2/registry.k8s.io/kube-scheduler/manifests/sha256:a4afe5bf0e status=200 latency=450.065µs host=localhost:8080 ip=127.0.0.1 
INFO[0199] pulling manifest from upstream: "registry.k8s.io/kube-scheduler@sha256:019d7877d15b45951df939efcb941de9315e8381476814a6b6fdf34fc1bee24c" 
INFO[0201] echo server GET:/v2/registry.k8s.io/kube-scheduler/manifests/sha256:019d7877d1 status=200 latency=1.896723966s host=localhost:8080 ip=127.0.0.1 
INFO[0201] echo server GET:/v2/registry.k8s.io/kube-scheduler/blobs/sha256:406945b511 status=200 latency=989.121µs host=localhost:8080 ip=127.0.0.1 
INFO[0201] echo server GET:/v2/registry.k8s.io/kube-scheduler/blobs/sha256:aba5379b9c status=200 latency=1.206855ms host=localhost:8080 ip=127.0.0.1 
INFO[0201] echo server GET:/v2/registry.k8s.io/kube-scheduler/blobs/sha256:e5dbef90ba status=200 latency=1.072352ms host=localhost:8080 ip=127.0.0.1 
INFO[0201] echo server GET:/v2/registry.k8s.io/kube-scheduler/blobs/sha256:fbe9343cb4 status=200 latency=2.084094ms host=localhost:8080 ip=127.0.0.1 
INFO[0201] echo server GET:/v2/registry.k8s.io/kube-scheduler/blobs/sha256:fcb6f6d2c9 status=200 latency=971.832µs host=localhost:8080 ip=127.0.0.1 
INFO[0201] echo server GET:/v2/registry.k8s.io/kube-scheduler/blobs/sha256:e8c73c638a status=200 latency=843.471µs host=localhost:8080 ip=127.0.0.1 
INFO[0201] echo server GET:/v2/registry.k8s.io/kube-scheduler/blobs/sha256:1e3d9b7d14 status=200 latency=895.636µs host=localhost:8080 ip=127.0.0.1 
INFO[0201] echo server GET:/v2/registry.k8s.io/kube-scheduler/blobs/sha256:4aa0ea1413 status=200 latency=984.738µs host=localhost:8080 ip=127.0.0.1 
INFO[0201] echo server GET:/v2/registry.k8s.io/kube-scheduler/blobs/sha256:65efb1cabb status=200 latency=974.436µs host=localhost:8080 ip=127.0.0.1 
INFO[0201] echo server GET:/v2/registry.k8s.io/kube-scheduler/blobs/sha256:13547472c5 status=200 latency=1.461483ms host=localhost:8080 ip=127.0.0.1 
INFO[0201] echo server GET:/v2/registry.k8s.io/kube-scheduler/blobs/sha256:53f492e4d2 status=200 latency=2.265104ms host=localhost:8080 ip=127.0.0.1 
INFO[0201] echo server GET:/v2/registry.k8s.io/kube-scheduler/blobs/sha256:6523efc24f status=200 latency=70.020159ms host=localhost:8080 ip=127.0.0.1 

Note the occurrence of pulling manifest from upstream in the logs above.

Stop the server with CTRL-C if running the binary or docker stop if running the container. Then simply re-run the server with the same command. In your other console window, remove the image from Docker's cache so that on the next pull, Docker has to go back through the Ociregistry.

docker images --format "{{.ID}}" localhost:8080/registry.k8s.io/kube-scheduler:v1.29.1\
  | xargs docker rmi

Re-pull the same image with the same docker command:

docker pull localhost:8080/registry.k8s.io/kube-scheduler:v1.29.1

The more recent Ociregistry log entries show:

INFO[0784] get /v2/                                     
INFO[0784] echo server GET:/v2/ status=200 latency=36.735µs host=localhost:8080 ip=127.0.0.1 
INFO[0784] serving manifest from cache: "registry.k8s.io/kube-scheduler:v1.29.1" 
INFO[0784] echo server HEAD:/v2/registry.k8s.io/kube-scheduler/manifests/v1.29.1 status=200 latency=127.739µs host=localhost:8080 ip=127.0.0.1 
INFO[0784] serving manifest from cache: "registry.k8s.io/kube-scheduler@sha256:a4afe5bf0eefa56aebe9b754cdcce26c88bebfa89cb12ca73808ba1d701189d7" 
INFO[0784] echo server GET:/v2/registry.k8s.io/kube-scheduler/manifests/sha256:a4afe5bf0e status=200 latency=169.374µs host=localhost:8080 ip=127.0.0.1 
INFO[0784] serving manifest from cache: "registry.k8s.io/kube-scheduler@sha256:019d7877d15b45951df939efcb941de9315e8381476814a6b6fdf34fc1bee24c" 
INFO[0784] echo server GET:/v2/registry.k8s.io/kube-scheduler/manifests/sha256:019d7877d1 status=200 latency=171.32µs host=localhost:8080 ip=127.0.0.1 
INFO[0784] echo server GET:/v2/registry.k8s.io/kube-scheduler/blobs/sha256:406945b511 status=200 latency=288.877µs host=localhost:8080 ip=127.0.0.1 
INFO[0784] echo server GET:/v2/registry.k8s.io/kube-scheduler/blobs/sha256:e5dbef90ba status=200 latency=381.53µs host=localhost:8080 ip=127.0.0.1 
etc...

You can see that pulling manifest from upstream from the first pull has been replaced with serving manifest from cache.

The image store

To view the image store: find /tmp/images. There you will see the manifests and blobs comprising the cache. To clean up, simply stop the server and rm -rf /tmp/images. Everything the server persisted to the file system is under that one directory.

Full Documentation

The full documentation in the GitHub Pages site covers the quick start above and many other topics:

  • Quick Start for the Helm chart
  • Configuring The Server
  • The Command Line
  • Loading And Pre-Loading
  • Pruning The Image Cache
  • Design (including Concurrency Design)
  • Airgap Considerations
  • Administrative REST API
  • Running Ociregistry as a systemd Service

Extension points exported contracts — how you extend this code

ManifestComparer (FuncType)
Type ManifestComparer applies a function to the passed manifest holder and returns true if the manifest matches the func
impl/cache/query.go
CacheEntryHandler (FuncType)
CacheEntryHandler defines a function that can act on a ManifestHolder instance from the metadata cache
impl/serialize/serialize.go

Core symbols most depended-on inside this repo

Close
called by 20
impl/cache/prune.go
Url
called by 20
impl/pullrequest/pullrequest.go
CreateDirs
called by 18
impl/serialize/serialize.go
NewPullRequestFromUrl
called by 17
impl/pullrequest/pullrequest.go
MhToFilesystem
called by 14
impl/serialize/serialize.go
GetImagePath
called by 14
impl/config/config.go
handleV2ManifestsReference
called by 11
impl/handlers.go
SetConfigFromStr
called by 11
impl/config/config.go

Shape

Function 240
Method 161
Struct 50
FuncType 6
TypeAlias 4
Interface 2

Languages

Go100%

Modules by API surface

api/ociregistry.gen.go104 symbols
impl/ociregistry.go40 symbols
impl/config/config.go38 symbols
impl/cache/cache.go24 symbols
api/models/models.gen.go17 symbols
impl/cache/prune.go15 symbols
testing/load/driver/runtests.go14 symbols
impl/cache/query.go11 symbols
testing/load/driver/registry.go10 symbols
mock/tls.go10 symbols
impl/serialize/serialize.go9 symbols
impl/pullrequest/pullrequest.go9 symbols

For agents

$ claude mcp add ociregistry \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact