How to use the built-in registry

Having a private Docker registry can significantly improve your productivity by reducing the time spent in uploading and downloading Docker images. The registry shipped with MicroK8s is hosted within the Kubernetes cluster and is exposed as a NodePort service on port 32000 of the localhost. Note that this is an insecure registry and you may need to take extra steps to limit access to it.

Working with MicroK8s’ built-in registry

You can install the registry with:

microk8s enable registry

The add-on registry is backed up by a 20Gi persistent volume is claimed for storing images. To satisfy this claim the storage add-on is also enabled along with the registry.

From version 1.18.3 it is also possible to specify the amount of storage to be added. E.g., to use 40Gi:

microk8s enable registry:size=40Gi

The containerd daemon used by MicroK8s is configured to trust this insecure registry. To upload images we have to tag them with localhost:32000/your-image before pushing them:

We can either add proper tagging during build:

Note: The :registry tag used below is just an example. Any tag can be used. However, containerd will cache them when pulling from the registry, and subsequent pushes won’t have any effect on Pods running inside of MicroK8s. You can either manually update the containerd image with microk8s ctr image pull localhost:32000/mynginx:registry, or use the :latest (or no) tag, which containerd will not cache.

docker build . -t localhost:32000/mynginx:registry

Or tag an already existing image using the image ID. Obtain the ID by running:

docker images

The ID is listed in the output:

REPOSITORY          TAG                 IMAGE ID            SIZE
mynginx             local               1fe3d8f47868        16.1MB
....

Then use the tag command:

docker tag 1fe3d8f47868 localhost:32000/mynginx:registry

Now that the image is tagged correctly, it can be pushed to the registry:

docker push localhost:32000/mynginx

Pushing to this insecure registry may fail in some versions of Docker unless the daemon is explicitly configured to trust this registry. To address this we need to edit /etc/docker/daemon.json and add:

{
  "insecure-registries" : ["localhost:32000"]
}

The new configuration should be loaded with a Docker daemon restart:

sudo systemctl restart docker

At this point we are ready to microk8s kubectl apply -f a deployment with our image:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: localhost:32000/mynginx:registry
        ports:
        - containerPort: 80

What if MicroK8s runs inside a VM?

Often MicroK8s is placed in a VM while the development process takes place on the host machine. In this setup pushing container images to the in-VM registry requires some extra configuration.

Let’s assume the IP of the VM running MicroK8s is 10.141.241.175. When we are on the host the Docker registry is not on localhost:32000 but on 10.141.241.175:32000. As a result the first thing we need to do is to tag the image we are building on the host with the right registry endpoint:

docker build . -t 10.141.241.175:32000/mynginx:registry

If we immediately try to push the mynginx image we will fail because the local Docker does not trust the in-VM registry. Here is what happens if we try a push:

docker push  10.141.241.175:32000/mynginx
The push refers to repository [10.141.241.175:32000/mynginx]
Get https://10.141.241.175:32000/v2/: http: server gave HTTP response to HTTPS client

We need to be explicit and configure the Docker daemon running on the host to
trust the in-VM insecure registry. Add the registry endpoint in
/etc/docker/daemon.json:

{
  "insecure-registries" : ["10.141.241.175:32000"]
}

Then restart the docker daemon on the host to load the new configuration:

sudo systemctl restart docker

We can now docker push 10.141.241.175:32000/mynginx and see the image getting uploaded. During the push our Docker client instructs the in-host Docker daemon to upload the newly built image to the 10.141.241.175:32000 endpoint as marked by the tag on the image. The Docker daemon sees (on /etc/docker/daemon.json) that it trusts the registry and proceeds with uploading the image.

Consuming the image from inside the VM involves no changes:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: localhost:32000/mynginx:registry
        ports:
        - containerPort: 80

Reference the image with localhost:32000/mynginx:registry since the registry runs inside the VM so it is on localhost:32000.

Using the local registry from another node in a MicroK8s cluster

If you have joined up other machines into a cluster with the machine that has the registry, you need to change the configuration files to point to the IP of the master node:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: <IP of the master node>:32000/mynginx:registry
        ports:
        - containerPort: 80

Note: You will also need to manually edit the containerd TOML on each of the worker nodes to point to and trust this custom registry. Instructions for this are in the private registry instructions in the Configuring Micro8s section.

1 Like

So, in:

Is this still valid to say you need to put master node’s IP as there’s a repository service with nodePort at 32000?

I believe so, as the actual contents of the registry are only available at the master node. @kjackal?

I thought that actual content is where pod is deployed (it is not necessarily master node!). Documentation is not necessarily wrong as you can still use master node’s IP/domain name, but it doesn’t seem needed as because of NodePort every node can access the registry at localhost at port 32000.

BTW I did test it just for fun…

cool, I will take your word for it, but I will wait for @kjackal to confirm how its supposed to work :wink:

@clicky is right that the registry will be available on all nodes on localhost.

Note that setting up the build in registry in its current state in a cluster is not something we recommend because the storage used to store images is local so if the node hosting the registry pod is removed and/or the registry pod gets recreated on a different node all images will be lost and the registry will have to be repopulated.

It would also make sense to put the registry behind a floating IP via a load balancer (see the metallb addon)

Thanks! I just wanted to point out that documentation is, maybe, slightly behind the code (as isn’t it always! :slight_smile: )

I’ve easily sorted that problem out by adding persistent volume

apiVersion: v1
kind: PersistentVolume
metadata:
  name: registry-nfs
spec:
  capacity:
    storage: 50Gi
  accessModes:
    - ReadWriteMany
  storageClassName: nfs
  nfs:
    server: 192.168.4.10
    path: "/srv/k8s/volumes/registry"

and pointed to it in PersistentVolumeClaim of container-registry.registry-claim

1 Like

There is some security problem with using NodePort to access the “insecure registry” outside. The NodePort is binding to 0.0.0.0:32000, not localhost:32000. There is no way to limit NodePort to localhost only:


Unfortunately, the solution with “docker image save/import tar” is veeeeery slow.

In case you still want to build images on the same hosts where microk8s is running you can use kubectl port-forward to access the registry. You can even run kubectl port-forward under systemd and able to access to the registry at any time. All you need to do is get the original registry.yaml, change NodePort to regular ClusterIP:

apiVersion: v1
kind: Service
metadata:
  labels:
    app: registry
  name: registry
  namespace: container-registry
spec:
  type: ClusterIP
  selector:
    app: registry
  ports:
    - name: registry
      port: 5000

Then apply it and use

microk8s kubectl \
  --namespace=container-registry \
  port-forward service/registry 5000:5000
1 Like

I’ve easily sorted that problem out by adding persistent volume

@clicky Did you have to modify the registry.yml file (https://github.com/ubuntu/microk8s/blob/master/microk8s-resources/actions/registry.yaml) and replace “claimName: registry-claim” with “claimName: registry-nfs” on line #57? Also, did you face any issues with namespaces ( The registry seems to be deployed to a separate namespace “container-registry”)

Hi all,

My setup is microk8s v1.21.3 in a cluster of 3 VMs.

I believe I followed the instructions correctly, and I was able to push my image to the microk8s registry using docker push after using the insecure-registries config in the host.

My problem now is that deploying a pod using that image is getting the same “http response to https client” error that I got on the host before the daemon.json change for insecure-registries:

  Warning  Failed     5m22s (x4 over 6m52s)  kubelet            Failed to pull image "g-k8s:32000/lds:devel": rpc error: code = Unknown desc = failed to pull and unpack image "g-k8s:32000/lds:devel": failed to resolve reference "g-k8s:32000/lds:devel": failed to do request: Head "https://g-k8s:32000/v2/lds/manifests/devel": http: server gave HTTP response to HTTPS client

The runtime on the node is containerd (as installed by microk8s), so I believe I now have to do the same kind of “insecure-registries” configuration that I did for docker on the host, but for containerd inside the VM. The microk8s doc doesn’t seem to cover that, or I missed it. Any tips?

On the microk8s node, I can pull that image manually if I pass --plain-http to the command:

root@g-k8s:~# microk8s ctr images pull g-k8s:32000/lds:devel
ctr: failed to resolve reference "g-k8s:32000/lds:devel": failed to do request: Head "https://g-k8s:32000/v2/lds/manifests/devel": http: server gave HTTP response to HTTPS client
root@g-k8s:~# microk8s ctr images pull --plain-http  g-k8s:32000/lds:devel
g-k8s:32000/lds:devel:                                                            resolved       |++++++++++++++++++++++++++++++++++++++| 
manifest-sha256:41337115fca870226f1a5fbfcaeac4b1206c9821fc84f272e44c086b42f50693: done           |++++++++++++++++++++++++++++++++++++++| 
layer-sha256:9578fb9701e5b67dff4c21b498d0bfd220a38432f51006d3b8ae70fffe97ae79:    done           |++++++++++++++++++++++++++++++++++++++| 
config-sha256:7d004e8bea6eb486eb91b37f886dfa0fc85b250fba753e07addf4917973ebf32:   done           |++++++++++++++++++++++++++++++++++++++| 
layer-sha256:f22ccc0b8772d8e1bcb40f137b373686bc27427a70c0e41dd22b38016e09e7e0:    exists         |++++++++++++++++++++++++++++++++++++++| 
layer-sha256:3cf8fb62ba5ffb221a2edb2208741346eb4d2d99a174138e4afbb69ce1fd9966:    exists         |++++++++++++++++++++++++++++++++++++++| 
layer-sha256:e80c964ece6a3edf0db1cfc72ae0e6f0699fb776bbfcc92b708fbb945b0b9547:    exists         |++++++++++++++++++++++++++++++++++++++| 
layer-sha256:c67f49d56deb603bea8eb139eb81116c36562a5e7cbea8ee342731345e7c1388:    done           |++++++++++++++++++++++++++++++++++++++| 
layer-sha256:5cdd5069fdb7f0aafb1571b49f7ec9338244f86fc142d745406efa751cfdb1eb:    done           |++++++++++++++++++++++++++++++++++++++| 
layer-sha256:e5a7d274bee24b995d6831261e882ad617bc21ff60e907ab710f58162962488f:    done           |++++++++++++++++++++++++++++++++++++++| 
elapsed: 7.7 s                                                                    total:  441.6  (57.3 MiB/s)    
...

Ok, I checked /var/snap/microk8s/current/args/containerd-template.toml and it has an entry for localhost:32000, but not g-k8s:32000. I guess the last section of https://microk8s.io/docs/registry-built-in needs to also explain that one has to change that url in each node from localhost to the actual node ip, or else it won’t match the image url used to push.

UPDATE (this forum won’t let me add another reply, how rude)

Oh, ok, I’m sorry, the doc does say that in its last line:

And you need to manually edit the containerd TOML on the worker machines, per the private registry instructions to trust the insecure registry.

I missed that, since all other instructions had nice yaml examples up to this point.

thanks. I’ll try and make that stand out better

Is it possible to change the size of the PV used later on after registry addon has been added?

How do I get the IP of the VM running MicroK8s? I am on MacOs

Hi. MicroK8s uses multipass to create VMs on MacOS, so

multipass list

should give you the name of the created VM and

multipass info <name>

will give you info on the instance called <name>, including the IP address

Hope that helps, let me know if not