Pull-through cache

Fetch it once.
Serve it everywhere.

Model weights and container images cross your uplink once — not once per machine. One cache, two protocols, no changes to your code.

Muninn

The problem

Ten machines pulling the same 40 GB model is forty times the bandwidth and none of the benefit.

Every node re-downloads the same weights. Every CI runner re-pulls the same base image. The bytes are identical, the uplink is not free, and the second machine waits exactly as long as the first.

One fetch, shared live

Concurrent requests for the same file share a single upstream download — later arrivals stream from the partial file as it lands rather than queueing behind it.

Local disk speed

A cached hit is served off local storage. No round trip, no rate limit, no dependency on anyone else's uptime.

Nothing to rewrite

Point an environment variable at the cache, or prefix an image name. Your training scripts and Dockerfiles stay as they are.

Containers

Put the registry in the path. The reference says where it came from, so adding a registry needs no change here and no daemon configuration on your side.

# instead of docker.io/library/redis:7
docker pull cache.example.com/docker.io/library/redis:7

# any registry, same shape
docker pull cache.example.com/ghcr.io/your-org/api:v2
docker pull cache.example.com/quay.io/prometheus/busybox:latest

Models and datasets

One environment variable. Metadata is proxied; file bytes are intercepted and cached, so the second machine to want a checkpoint reads it off local disk.

# unchanged code, now served from cache
export HF_ENDPOINT=https://cache.example.com

python -c "from huggingface_hub import snapshot_download;
           snapshot_download('meta-llama/Llama-3.1-8B')"
Muninn ingests from the Hub using its native transfer protocol — parallel range requests — and serves your LAN plain HTTP. A normal reverse proxy cannot do this: its upstream leg inherits the client's protocol, and forcing the cacheable path collapses the download to a single slow stream.

Built for the boring parts

Integrity, not optimism

Container blobs are digest-verified before they are kept. Corrupt bytes are discarded rather than cached and served forever.

Eviction that knows better

Least-recently-used, except for repositories deleted upstream — evicting a live one costs a re-download, evicting a vanished one destroys the last copy.

Per-key authorisation

Credentials are keys with rules. A key can be allowed to pull one registry and nothing else, and revoking it takes effect on the next request.

Runs where your data is

A single container with a volume. No control plane, no external dependencies, no phone-home.

Your credentials

Sign in to create and revoke the keys you use against this cache. Nobody needs shell access on the cache to rotate one.

What it is not

A cache is shared storage, not a security boundary. Everyone whose key permits a path is served whatever is cached there — Muninn does not re-check entitlement with the upstream registry on a hit, because on a hit it never contacts the upstream at all. If two tenants must not read each other's private images, give them separate caches.

It is also not a mirror. Nothing is pre-populated and nothing is guaranteed to still be here; it holds what has been asked for, and evicts under pressure.