Compose a virtual registry
A virtual OCI index lists other indexes as layers and serves them under one route. Resolution walks the layers in
order and keeps the first member that holds an image, so a hosted member listed ahead of a proxy shadows any same-named
image upstream. This is the shadowing rule, the dependency-confusion
defense, applied to containers: a pull of a name you published serves your image, and anything you have not published
falls through to the upstream. See the index model for the semantics.
Configure the stack
Three indexes: a proxy that caches Docker Hub, a hosted store for your own images, and a
virtual index that stacks them hosted-first. Members and the virtual index must share the oci ecosystem.
# peryx.toml
host = "127.0.0.1"
port = 4433
[[index]] # proxy: cache Docker Hub
name = "dockerhub"
route = "dockerhub"
ecosystem = "oci"
[[index.upstream]]
name = "primary"
url = "https://registry-1.docker.io"
[[index]] # hosted: your own images
name = "images"
route = "images"
ecosystem = "oci"
hosted = true
[[index.access_token]]
name = "upload"
secret = "<token>"
actions = ["write", "delete"]
[[index]] # virtual: hosted shadows the proxy
name = "reg"
route = "reg"
ecosystem = "oci"
layers = ["images", "dockerhub"]
upload = "images"
Run it with peryx serve --config peryx.toml. Clients now read and write one route, reg.
layers defines resolution order. With images before dockerhub, hosted images shadow matching Docker Hub images.
Reversing the order gives Docker Hub precedence. upload names the hosted layer that receives pushes to the virtual
route; when omitted, peryx selects the first hosted layer. A virtual index containing only proxies rejects pushes.
Transport
docker and podman trust a loopback registry (localhost,
127.0.0.0/8) over plain HTTP with no configuration, so on the same host the commands below work as written. Over the
network (or from Docker Desktop, whose engine runs in a VM where the host's localhost is not the engine's), a client
demands HTTPS. Give peryx a certificate (serve HTTPS) or set the client's
insecure-registry option. crane and podman take a per-command flag, shown below; docker needs
insecure-registries in its daemon config.
Pull through the virtual route
A pull of reg walks the members hosted-first. The name decides the source:
# your build if you pushed `my-app` to the hosted layer, otherwise Docker Hub's:
docker pull 127.0.0.1:4433/reg/my-app:1.0
# Docker Hub because you have not published nginx, so it falls through:
docker pull 127.0.0.1:4433/reg/library/nginx:latest
Once you push my-app to the hosted layer, the name is shadowed: each pull of reg/my-app serves your image, and a
same-named image on Docker Hub cannot take its place. Publishing to the hosted layer turns the name off upstream; there
is no separate deny-list to maintain.
docker pull 127.0.0.1:4433/reg/my-app:1.0
podman pull --tls-verify=false 127.0.0.1:4433/reg/my-app:1.0
crane pull --insecure 127.0.0.1:4433/reg/my-app:1.0 my-app.tar
Push into the stack
A push to reg lands in the layer named by upload (here images), so one route reads and writes. peryx accepts any
username; the token is the Basic-auth password. Peryx streams blobs into the content-addressed store and verifies them
on commit. Mount the token at /run/secrets/peryx-token; each client reads it from standard input:
docker login 127.0.0.1:4433 -u _ --password-stdin < /run/secrets/peryx-token
docker tag my-app 127.0.0.1:4433/reg/my-app:1.0
docker push 127.0.0.1:4433/reg/my-app:1.0
podman login --tls-verify=false 127.0.0.1:4433 -u _ --password-stdin < /run/secrets/peryx-token
podman push --tls-verify=false my-app 127.0.0.1:4433/reg/my-app:1.0
crane auth login 127.0.0.1:4433 -u _ --password-stdin < /run/secrets/peryx-token
crane push --insecure my-app.tar 127.0.0.1:4433/reg/my-app:1.0
The pushed image is visible on both routes: reg/my-app (through the stack) and images/my-app (the hosted store
through its own route). The proxy at dockerhub is untouched; shadowing is a resolution rule, not a copy.
Failure behavior
If a member cannot answer, as with a down upstream and cold cache, peryx logs a warning and tries the next member. Images you host remain pullable during a Docker Hub outage. A proxy with a warm cache serves its cached copy.
Related
- The role and shadowing model: the index model
- The full three-role walkthrough: run a container registry
- The protocol and every client snippet: OCI ecosystem
- Serve trusted HTTPS so clients need no insecure flag: serve HTTPS