Run a container registry
peryx implements the OCI distribution protocol, so docker, podman, and crane can
pull and push through it. The configuration below defines cached, hosted, and virtual indexes. Install peryx first; see
Getting started.
Configure the indexes
An index has one of three roles and names
the oci plugin. This config declares all three: a proxy of Docker Hub, a hosted store for your own images, and a
virtual index that stacks them:
# 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"]
Run it with peryx serve --config peryx.toml.
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. For that, 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 proxy
A pull of library/alpine through the dockerhub route runs the upstream's bearer-token handshake, verifies the
digest, caches every blob, and serves later pulls from disk:
docker pull 127.0.0.1:4433/dockerhub/library/alpine:latest
podman pull --tls-verify=false 127.0.0.1:4433/dockerhub/library/alpine:latest
crane pull --insecure 127.0.0.1:4433/dockerhub/library/alpine:latest alpine.tar
Docker Hub official images work by their short name too, since the cached index resolves ubuntu to library/ubuntu
before it asks Hub:
docker pull 127.0.0.1:4433/dockerhub/ubuntu:latest
See mirror Docker Hub official images for the setting behind that and when to override it.
Push your own images
Pushing needs a write-granting [[index.access_token]] on the hosted index; peryx accepts any username, and the token's
secret is the Basic-auth password. Mount the token at /run/secrets/peryx-token; Docker,
Podman, and Crane read it from standard input instead of exposing it in the process
arguments. Peryx streams blobs into the content-addressed store and verifies them on commit:
docker login 127.0.0.1:4433 -u _ --password-stdin < /run/secrets/peryx-token
docker tag my-app 127.0.0.1:4433/images/my-app:1.0
docker push 127.0.0.1:4433/images/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/images/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/images/my-app:1.0
The access token gates writes only. Reads are open: anyone who can reach the route can pull an image you pushed, so restrict who reaches peryx at the network layer (or front it with TLS) when a hosted index holds private images.
Combine both with a virtual index
Pull through the reg route and peryx walks the members hosted-first: an image you pushed to images wins over a
same-named one on Docker Hub, and anything you have not published falls through to the upstream. This is
shadowing, the dependency-confusion defense, applied to containers:
# your own build of `my-app` if you pushed it, otherwise Docker Hub's:
docker pull 127.0.0.1:4433/reg/my-app:1.0
# Docker Hub because you have not published nginx:
docker pull 127.0.0.1:4433/reg/library/nginx:latest
A push to reg lands in the hosted layer, so clients read and write one route.
Delete an image
A hosted index with volatile = true (the default) accepts deletes. crane delete removes a manifest by digest; peryx
answers 202 and later pulls of that digest return 404:
crane delete --insecure 127.0.0.1:4433/images/my-app@sha256:<digest>Related
- Protocol, roles, and client examples: OCI ecosystem
- Serve trusted HTTPS so clients need no insecure flag: serve HTTPS
- What ships per ecosystem: capability matrix
- Why Hub needs the
library/namespace, and what an upstream401means: Docker Hub names and upstream auth