Documentation menu

Registry mirrors

Every klimax VM runs a set of pull-through registry caches, wired into each cluster automatically. They exist for one reason: so you stop hitting Docker Hub's rate limits and stop re-downloading the same images every time you rebuild a cluster.

Why registry mirrors

A klimax workflow means creating and destroying clusters constantly. Without mirrors, every rebuild re-pulls the same base images (nginx, metallb, your app's dependencies) straight from the public registries. Two problems follow:

  • Docker Hub rate limits. Anonymous pulls from docker.io are throttled per source IP. On a shared network — an office, a VPN, CI — you can burn through the quota fast and start seeing toomanyrequests: You have reached your pull rate limit.
  • Cold-cache rebuilds are slow. Re-pulling hundreds of megabytes on every cluster create wastes minutes and bandwidth you don't need to spend.

klimax fixes both by running a pull-through cache in front of each upstream registry. The first pull of an image fetches it from the internet and stores the blobs locally; every pull after that — across every cluster, and across cluster rebuilds — is served from that local cache. With the default host-backed cache, the blobs even survive a full klimax destroy.

Authenticate to lift the Docker Hub limit further

Caching already collapses repeated pulls into one, but the very first pull of each image still counts against the anonymous quota. Add your Docker Hub credentials to the registry-dockerio mirror to pull as an authenticated user, which raises the limit substantially:

registries:
  mirrors:
    - name: "registry-dockerio"
      port: 5030
      remoteURL: "https://registry-1.docker.io"
      username: "your-dockerhub-username"   # optional
      password: "your-dockerhub-password"   # optional (use an access token)
🔑

Prefer a Docker Hub access token over your account password — it's scoped and revocable. The credential lives in ~/.klimax/config.yaml and is used only by the mirror container inside the VM.

How clusters use them

The mirrors run as plain registry:2 containers inside the VM, each joined to the kind Docker network. When klimax creates a cluster it configures that cluster's containerd — via certs.d hosts.toml files (the containerd 2.x way) — so pulls from docker.io, quay.io, gcr.io, and Google Artifact Registry (us-docker.pkg.dev, us-central1-docker.pkg.dev) are transparently redirected to the matching mirror. Because every kind node and every mirror share the kind network, nodes resolve mirrors by hostname (e.g. registry-dockerio:5030) via Docker's embedded DNS — no per-pod config, no image-name rewriting in your manifests.

kind node containerd pull docker.io/nginx registry-dockerio :5030 pull-through cache docker.io upstream only on cache miss ~/.klimax/registry-cache certs.d redirect on the "kind" network first pull only PULL docker.io/nginx
A pull from a kind node. The mirror serves cached blobs and only reaches the upstream the first time an image is seen.

Adding a registry mirror

Any registry with a standard v2 API can be mirrored. Append an entry to registries.mirrors with a unique name, a free host port, and the upstream remoteURL — for example GitHub Container Registry:

registries:
  mirrors:
    # ...the defaults...
    - name: "registry-ghcrio"
      port: 5060
      remoteURL: "https://ghcr.io"
      username: "your-github-username"   # optional, if the upstream needs auth
      password: "ghp_..."

Apply it by recreating the mirror containers, then rebuild any clusters that should route through the new mirror (the containerd patch is applied at cluster-create time):

$ klimax down && klimax up      # restart the registry containers
$ klimax cluster create dev     # new clusters pick up the mirror

Don't name a mirror after its hostname. A mirror container joins the shared kind Docker network, so a name like quay.io would make Docker's embedded DNS resolve quay.io to the container itself — the proxy then resolves its own upstream to itself and pulls fail. klimax rejects any mirror name containing . or :; use a safe form like registry-quayio. (Renaming a mirror also means removing its old container — docker rm -f it, then klimax up.)

🚫

Set mirrors to an empty list ([]) to disable all mirrors and pull straight from the upstreams.

The defaults

Out of the box, klimax runs five pull-through mirrors. You don't configure any of this to get started — it's the default registries block:

ContainerPortRole
registry-dockerio5030pull-through cache for https://registry-1.docker.io
registry-quayio5010pull-through cache for https://quay.io
registry-gcrio5020pull-through cache for https://gcr.io
registry-us-docker-pkgdev5040pull-through cache for https://us-docker.pkg.dev
registry-us-central1-docker-pkgdev5050pull-through cache for https://us-central1-docker.pkg.dev

The cache location is set by registries.cacheStorage, which defaults to "host". See Configuration for the complete block.

Getting your own images into a cluster

klimax has no local push registry — load a locally built image straight into a cluster's nodes with kind load. Point your host Docker at the VM, build there, then load the image (the kind CLI lives in the VM, so run it from klimax shell):

$ eval "$(klimax docker-env)"                 # DOCKER_HOST → the klimax VM
$ docker build -t myapp:test .                # image now lives in the VM's Docker
$ klimax shell                                # open an SSH session in the VM
$ kind load docker-image myapp:test --name dev

Then reference it in a manifest as myapp:test and pin the pull policy so the node uses the loaded image instead of pulling from a registry:

  image: myapp:test
  imagePullPolicy: IfNotPresent
🔌

Public images pull transparently through the built-in mirrors above — no extra steps. kind load is only for images you build locally and haven't pushed anywhere.

Cache persistence

Where the mirror cache lives is controlled by registries.cacheStorage:

  • "host" (default) — cache at ~/.klimax/registry-cache/<name>/ on the Mac, virtiofs-mounted into the VM. Survives klimax down/up and klimax destroy.
  • "guest" — cache at /var/lib/klimax/registry-cache/<name>/ inside the VM only; wiped on klimax destroy.

With host storage, blobs persist across full teardowns — rebuilding a lab pulls from the local cache instead of the internet, and that first cold pull is a one-time cost.

Resetting the cache

To wipe every mirror's cache and start fresh:

$ klimax registry clean-cache

This removes all mirror containers and deletes their cache directories (on the host or in the guest, per cacheStorage); re-run klimax up to recreate them cold. To wipe everything — VM, clusters, and cache — see wiping and reinstalling.

See also