Documentation

Proxy a private registry

A cached OCI index reads through to an upstream registry. When that upstream is private, peryx needs credentials to run its bearer-token handshake. Put the credentials on the index so clients pull through peryx and present peryx's own auth, or none. This keeps the upstream secret in one process instead of on each developer's machine.

Index configuration

A cached OCI proxy is an [[index]] with ecosystem = "oci", a route, and an [[index.upstream]] url pointing at the upstream's registry root. Add the credential fields the upstream expects to that source:

# peryx.toml
[[index]]
name = "ghcr"
route = "ghcr"
ecosystem = "oci"

[[index.upstream]]
name = "primary"
url = "https://ghcr.io"
username = "<user>"
token = "<pat>"

peryx supports three credential fields on the [[index.upstream]] source:

  • username and password: Basic-auth credentials peryx presents when the upstream's WWW-Authenticate challenge asks for them.
  • token: a bearer token that peryx sends as supplied. It takes precedence over username/password when both are set.

Which you set depends on the upstream:

  • GHCR: username = "<github-user>", token = "<personal-access-token>" (a PAT with read:packages).
  • Amazon ECR: an [[index.upstream]] url = "https://<account>.dkr.ecr.<region>.amazonaws.com", with username = "AWS" and the password from aws ecr get-login-password. ECR tokens are short-lived; rotate the value on the schedule the token's lifetime demands.
  • Artifactory or Harbor: point the [[index.upstream]] url at the registry's /v2/ root and set username/password, or token if the server issues bearer tokens.

Keep secrets out of the file

The credential fields hold literal strings, so a token in peryx.toml is a secret at rest. Restrict the file (chmod 600 peryx.toml) and keep it out of version control. To avoid a plaintext token on disk, render the config from a template at deploy time, injecting the value from a PERYX-scoped environment variable or a secret manager. See configuration for the precedence tiers and file-loading rules.

Pull

Assume peryx runs at 127.0.0.1:4433. Docker and Podman trust a loopback registry over plain HTTP with no setup, so a pull on the same host needs no client configuration; crane and podman use an insecure flag. Over the network, serve TLS so clients need no flag.

Pull through peryx's route prefix; the upstream repository name follows it:

docker pull 127.0.0.1:4433/ghcr/<owner>/<image>:latest
podman pull --tls-verify=false 127.0.0.1:4433/ghcr/<owner>/<image>:latest
crane pull --insecure 127.0.0.1:4433/ghcr/<owner>/<image>:latest image.tar

peryx authenticates to the private upstream with the index's credentials, verifies each manifest and blob digest, stores them, and serves them back. Clients do not receive the upstream secret; later pulls come from disk. Concurrent pulls of one uncached layer share a single upstream fetch.

Upstream rejection

If the upstream rejects the index's credentials, the pull fails with 401 and the UNAUTHORIZED code, carrying a message that names the upstream rather than reporting a missing manifest. A cached index asks no credentials of its own clients, so read that 401 as the upstream refusing peryx: a wrong or expired username/password/token, an account that cannot see the repository, or a repository name the upstream will not serve.

An image already in the cache remains available. A cached tag whose revalidation returns 401 can serve stale content within max_stale_secs, so an expired credential degrades pulls of new images while the working set keeps running. See Docker Hub names and upstream auth.

Client-facing auth

The upstream credentials are separate from what clients present to peryx. A cached index does not require clients to authenticate; anyone who can reach the route can pull through it. Restrict who reaches peryx at the network layer, or front the cache with a virtual index when you need to combine it with a hosted store.

On this page