Docker Registry Usage¶
Relaycache works well as a caching proxy in front of a Docker / OCI registry. This page covers the specifics of the registry API and how Relaycache handles each request type.
Docker pull request sequence¶
A docker pull is not a single request. It is a structured sequence:
1. API version check¶
GET /v2/
→ 200 OK (no auth required)
→ 401 Unauthorized (with Www-Authenticate pointing to token endpoint)
Relaycache passes this through. It is typically not cacheable (no body worth
caching, or Cache-Control: no-cache).
2. Token fetch¶
The client fetches a short-lived bearer token from an auth server
(often auth.docker.io or a separate host). This is a different host
from the registry — Relaycache only proxies requests to --upstream, so token
fetches are not proxied at all.
3. Manifest fetch¶
GET /v2/<name>/manifests/<reference>
Accept: application/vnd.oci.image.manifest.v1+json, ...
Authorization: Bearer <token>
The reference is either a tag (latest) or a digest (sha256:abc123).
Relaycache behaviour:
Caches
200responsesRegistry sends
Vary: Accept— Relaycache creates separate cache entries perAcceptvalueETag = content digest — validators work perfectly
Tags are mutable; when
latestchanges, the origin returns200with a new body (new digest → new ETag) and Relaycache updates the cache
4. Config blob fetch¶
GET /v2/<name>/blobs/sha256:<digest>
Authorization: Bearer <token>
Small JSON file describing the image config. URL contains the digest → fully content-addressed → immutable. Relaycache caches this.
5. Layer blob fetches¶
GET /v2/<name>/blobs/sha256:<digest>
Authorization: Bearer <token>
Range: bytes=0-10485759 (often, for large layers)
These can be very large (hundreds of MB). The URL contains the digest → immutable content.
Range handling: On first fetch, Relaycache ignores the Range header and
downloads the full layer, caches it, then serves the requested range as
206. Subsequent range requests for the same layer are served from the
cache with a round-trip to the registry for auth only (no body re-download).
Large layers (> --max-cacheable-size) are passed through as-is without
caching.
Summary table¶
Request |
Cached? |
Notes |
|---|---|---|
|
❌ |
Auth challenge, no useful body |
Manifest by tag |
✅ |
Mutable tag; ETag = digest |
Manifest by digest |
✅ |
Immutable; ETag = digest |
Config blob |
✅ |
Small, immutable |
Layer blob (≤ max-cacheable-size) |
✅ |
Full-fetch upgrade on first range |
Layer blob (> max-cacheable-size) |
❌ |
Pass-through |
Token endpoint |
❌ |
Different host, not proxied |
|
❌ |
Non-GET |
|
❌ |
Non-GET |
Example configuration¶
relaycache \
--upstream https://registry-1.docker.io \
--bind 0.0.0.0:5000 \
--cache-dir /var/cache/relaycache \
--max-cacheable-size 2GiB \
--entry-ttl 7days \
--eviction-interval 6h
Configure Docker to use the proxy:
// /etc/docker/daemon.json
{
"registry-mirrors": ["http://your-relaycache-host:5000"]
}
Or for a private registry, configure --upstream to point to it and have
clients use your-relaycache-host:5000 as the registry address.
Auth note¶
Docker clients send a fresh bearer token on every request. The token is passed through to the registry on every request (Relaycache always forwards all headers). The registry validates the token on every request, meaning revoked access takes effect immediately — there is no window where a cached response is served to an unauthorised client.