My Profile Photo

Sheogorath's Blog

Mirroring your cluster images


When you run a Kubernetes cluster in production and you happen to misconfigure a deployment it can quickly happen, that you hit the Docker Hub rate limits. But it can also happen, that an upstream registry is just unavailable or an image has been deleted. In order to counter this, you can set up harbor, a common Kubernetes registry, which allows to transparently cache content. With cri-o configured to automatically check this registry, you can get automatic mirroring done, without touching any manifest or manually editing image strings.

Before you start

It’s expected that you already have harbor deployed in your cluster. It’s easy to deploy, so I’ll leave it to you. Obviously it’s also expected that your Kubernetes cluster is already running and that it’s using cri-o as container runtime.

Set up a cache project

Setup a registry endpoint for e.g. Docker Hub, without authentication.

Image registry endpoint, showing a setup of Docker Hub without authentication

Next setup a project that is configured as “Proxy Cache” for the endpoint created in the previous step. In this case we call it dockerhub. If you want, you can setup a resource quota, which helps to keep the storage use limited.

Project creation screen with the name filled out as dockerhub and the Proxy Cache box checked

Configure cri-o to use the cache

Now it’s time to configure cri-o to resolve the image registries automatically without being reconfigured. This is done by using the registries.conf’s mirror feature.

Just take the following snippet, replace <your harbor registry> with the domain of your harbor setup, and drop it at /etc/containers/registries.conf.d/mirrors.conf on each of your Kubernetes nodes:

prefix = ""
location = ""
  location = "<your harbor registry>/dockerhub"

Now restart cri-o (or the node altogether) and it should be picked up automatically.

Note: This will only take care of images that are pulled without credentials.

Next Steps

With the basics in place and basically no downsides, since cri-o will automatically try the local mirror, and if that fails pull directly from upstream, you can explore all other repositories in your cluster and add mirrors for these as well.

Use this snippet from the Kubernetes manual which will give you all images currently running in your cluster and check the registries used:

kubectl get pods --all-namespaces -o jsonpath="{.items[*].spec.containers[*].image}" | tr -s '[[:space:]]' '\n' | sort | uniq -c

Alternatively, you can ask your local Prometheus instance for the images used in the cluster using following query:

count(label_replace(kube_pod_container_info, "image", "$1", "image", "([^/]+)/.*")) by (image)

Just repeat the steps above with the other registry and quickly you’ll have a full mirror of all images in your cluster.


It’s very easy to get a full mirror of images locally in your cluster, when utilising the cri-o features. (These work for podman as well, by the way.) Which makes your cluster a lot more resilient regarding outside influences. You also get vulnerability scanning for free.