Adding a Private Docker Registry to your K3s Homelab
14 May 2026 · 3.045 min read

docker

In my previous post, I described Building a Kubernetes Raspberry Pi Homelab with K3s. By default, the cluster pulls public images from Docker Hub, which, for personal projects, may be acceptable. But if you want to work on anything more commercially sensitive, then you’ll want to use a private Docker registry to host your images.

If you recall, in my last post, I mentioned that I purchased a larger (256 GB) SD card for the master node, as I intended it to also house a private Docker registry. In this post, I’ll describe the steps you need to take to set that up.

Create the Registry

To create a registry, we need a ReplicationController that mounts a registry container on the master node.

We also need to make sure that the registry is only ever created on the master node, and to do that we use a nodeSelector that targets the preferred node with a given label. Fortunately, K3s already labels the master node with node-role.kubernetes.io/master=true, so targeting it becomes a trivial task.

Images will be stored in a volume at /var/lib/registry which will persist the registry state after a system restart.

We’ll also create a Service to expose the registry on port 5000.

Get the gist…

Save this gist as kube-registry.yaml to your local machine and apply it to your cluster with:

kubectl apply -f kube-registry.yaml

Configure K3s

As you access the cluster using a hostname from your local machine, you’ll need to configure K3s so that it can resolve the registry hostname to an IP address:

Do this by adding the following /etc/rancher/k3s/registries.yaml file on each node of your cluster:

mirrors:
  "<hostname>:5000":
    endpoint:
      - "http://<ip-address>:5000"

I used k3s as an alias for my master node in /etc/hosts:

192.168.1.10 k3s-0 k3s
192.168.1.11 k3s-1
192.168.1.12 k3s-2
192.168.1.13 k3s-3

So my registries.yaml looks like this:

mirrors:
  "k3s:5000":
    endpoint:
      - "http://192.168.1.10:5000"

Restart your cluster with sudo systemctl restart k3s on the master node.

Configure Docker

Finally, configure your local Docker client by adding:

"insecure-registries": ["<hostname>:5000"]

…to your Docker Engine preferences.

Test It Out

We’ll reuse the demo k3s-cluster-demo application from my previous post. It’s a JavaScript client with a Node server API that just returns the server’s hostname for display.

Now that we’ve got a private registry, we’ll need to configure the infra scripts to point to it with:

./configure.sh <hostname>:5000

You are now able to push images into your private registry and deploy them to your cluster with:

skaffold run

And that’s it. You can now list the images in your private registry with:

curl --request GET --url http://<hostname>:5000/v2/_catalog

…and you should get the following response:

{
  "repositories": [
    "k3s-cluster-demo_client",
    "k3s-cluster-demo_server"
  ]
}

Finally…

What I like most about this setup is that the cluster starts to feel far more cohesive and self-sufficient. Images stay within your own network, deployments become faster and more predictable, and it creates a much cleaner foundation for CI/CD and GitOps workflows later on. There’s also a noticeable shift in developer experience. Building, pushing, and deploying containers locally becomes simpler and more reliable, especially when working across multiple Raspberry Pi nodes.

It also changes the way you think about the cluster operationally. Before adding a private registry, deployments can feel fairly manual and experimental. Afterwards, the environment becomes much more declarative and platform-like, with clearer ownership of the build and deployment pipeline. For a home lab, that’s where the real value starts to emerge. You move beyond simply running containers and begin building a small but fully integrated engineering platform.