How To Install A Private Docker Container Registry In Kubernetes

IMPORTANT

I recreated this tutorial in a simple way to work with a newer version of Traefik. The updated tutorial can be found here:

How To Setup Docker Registry In Kubernetes Using Traefik v2
Use Traefik v2 To Self-Host A Docker Container Registry In Kubernetes

Introduction

Hosting a Docker private registry gives you full control over the storage location of your images and how you can access them. This is especially useful if you develop your own private projects and don't want to host them publicly on Docker Hub.

In this tutorial, you will learn how to install a private Docker registry in any Kubernetes cluster. It is a follow-up of a tutorial that was previously released on this blog because it uses a Traefik Ingress Controller to expose the Docker registry.

Preparation

Create Kubernetes Namespace

The first step is to create a Kubernetes namespace where all resources will be applied during this tutorial:

kubectl create namespace docker-registry

PersistentVolumeClaim

In this section, you will mount a volume into a dedicated Kubernetes Pod using a PersistentVolumeClaim. A PersistentVolumeClaim (PVC) is a Kubernetes resource to use a pre-defined abstract PersistentVolume (PV) storage without exposing how those volumes are implemented.

💡
Prerequisite: To further follow this tutorial a PersistentVolume should exist within the Kubernetes cluster. A PersistentVolume (PV) is a storage definition that was either manually provisioned by a user or dynamically provisioned using a storage class. See this link for further information.

To set up the PersistentVolumeClaim usable in the private Docker registry this tutorial will assume that you already have a preconfigured PersistentVolume called csi-cinder-classic.

1. Create a new file (registry-pvc.yaml) containing the PersistentVolumeClaim using the csi-cinder-classic storage class:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: docker-registry-pv-claim
  namespace: docker-registry
spec:
  accessModes:
    - ReadWriteOnce
  volumeMode: Filesystem
  resources:
    requests:
      storage: 60Gi
  storageClassName: csi-cinder-classic

2. Apply it to the Kubernetes Cluster:

kubectl apply -f registry-pvc.yaml

3. Verify if the persistent volume claim was successfully created:

kubectl get pvc docker-registry-pv-claim -n docker-registry

# NAME              					STATUS   VOLUME		CAPACITY   ACCESS MODES   STORAGECLASS
# docker-registry-pv-claim		Bound    ***			60Gi       RWO            csi-cinder-classic

Set Up Docker Registry

Now, that you set up the namespace and the PersistentVolumeClaim you can deploy a Docker registry using the external storage and make the registry available across the entire Kubernetes cluster.

Generate User & Password

To set up the Docker registry you need to generate a user and password which will be used to push and pull images to the registry.

Create a new file gen-pass.sh that contains the following content:

export REGISTRY_USER=admin
export REGISTRY_PASS=registryPass
export DESTINATION_FOLDER=./registry-creds
   
# Backup credentials to local files (in case you'll forget them later on)
mkdir -p ${DESTINATION_FOLDER}
echo ${REGISTRY_USER} >> ${DESTINATION_FOLDER}/registry-user.txt
echo ${REGISTRY_PASS} >> ${DESTINATION_FOLDER}/registry-pass.txt
   	
docker run --entrypoint htpasswd registry:2.7.0 \
    -Bbn ${REGISTRY_USER} ${REGISTRY_PASS} \
    > ${DESTINATION_FOLDER}/htpasswd
      
unset REGISTRY_USER REGISTRY_PASS DESTINATION_FOLDER
💡
Note: Change REGISTRY_USER and REGISTRY_PASS to your needs. Also, since htpasswd was removed from the latest Docker images we will be using version 2.7.0 to generate user and password. For more information have a look at this stack overflow post.

Execute the script which will create the credentials within the subfolder ./registry-creds:

sh gen-pass.sh

Install Docker Registry Using Helm

In the following steps, you will use Helm to install the Docker registry.

💡
Note: Helm is the package manager for Kubernetes, focused on automating the installation of all kinds of Kubernetes applications.

1. Add the twuni/docker-registry Helm repository which is a successor of the official Docker registry:

helm repo add twuni https://helm.twun.io

2. Update local Helm chart repository cache:

helm repo update

3. Search for the latest version of twuni/docker-registry Helm chart version:

helm search repo docker-registry

# NAME                    CHART VERSION   APP VERSION     DESCRIPTION
# twuni/docker-registry   2.2.2           2.8.1           A Helm chart for Docker Registry

4. Create a registry-chart.yaml which will be used to install the Docker registry in our Kubernetes cluster:

---
replicaCount: 1
persistence:
  enabled: true
  size: 60Gi
  deleteEnabled: true
  storageClass: csi-cinder-classic
  existingClaim: docker-registry-pv-claim
secrets:
  htpasswd: admin:$2y$05$Gh/3ppmkuIXJIVyBBtHf0ug.wnnJvbtSEzlXz6z/7oO7XvF/xq7Ni
💡
Note: Replace the htpasswd string with the content of the file which was generated by the gen-pass.sh script (./registry-cred/htpasswd)

5. Install the Docker registry Helm chart using the registry-chart.yaml:

helm install -f .\registry-chart.yaml docker-registry --namespace docker-registry twuni/docker-registry

6. Verify installation:

kubectl get pods -n docker-registry

# NAME                              READY   STATUS    RESTARTS
# docker-registry-9fa1234ba-gaf16   1/1     Running   0

If you want to change something like the replicaCount, the htpasswd, or the storage you can do this by adjusting the registry-chart.yaml and reapply it by executing:

helm upgrade -f .\registry-chart.yaml docker-registry --namespace docker-registry twuni/docker-registry

How To Uninstall?

To uninstall the Docker registry you have to remove it from the Kubernetes cluster with Helm:

helm uninstall docker-registry --namespace docker-registry

Then, remove the Docker registry namespace from the Kubernetes cluster:

kubectl delete namespace docker-registry

Add Docker Registry Ingress

To expose the Docker registry you will use Traefik Ingress Controller which will allow access to the registry through HTTPS with a proper TLS certificate.

💡
Note: Read about how to install Traefik Ingress Controller in any Kubernetes cluster within this tutorial: https://www.paulsblog.dev/how-to-install-traefik-ingress-controller-in-kubernetes/

1. Create a new file (ingressroute.yaml) and make sure to replace YOUR_DOMAIN with your Docker registry domain:

---
kind: IngressRoute
apiVersion: traefik.containo.us/v1alpha1
metadata:
  name: docker-registry
  namespace: docker-registry

spec:
  entryPoints:
    - websecure

  routes:
    - match: Host(`YOUR_DOMAIN`)
      kind: Rule
      services:
        - name: docker-registry
          port: 5000

2. Apply the IngressRoute by executing:

kubectl apply -f ingressroute.yaml

3. Verify if the Kubernetes resource was successfully created:

kubectl describe ingressroute docker-registry -n docker-registry

Push Images To Private Registry In Kubernetes Cluster

To show how to push a Docker Image to our new Docker registry this tutorial show how you can pull a public Docker Hub image, tag it and then push it to your registry.

1. Log in to the Docker registry by using the previously created credentials

docker login \
   -u $(cat ./registry-creds/registry-user.txt) \
   -p $(cat ./registry-creds/registry-pass.txt) \
   YOUR_DOMAIN

2. Pull the nginx:latest Docker image:

docker pull nginx

3. Tag the image with a custom name and prepend the private Docker registry domain name

docker tag nginx YOUR_DOMAIN/my-nginx

4. Push the Docker Image to the registry

docker push YOUR_DOMAIN/my-nginx

Use Docker Registry To Pull Images In Your Kubernetes Cluster

As you have a Docker registry deployed in your Kubernetes cluster you can start using it by pulling previously pushed images for your Kubernetes Pods.

To learn how a private Docker registry can be used for pulling images you will create a simple Kubernetes pod in a newly created test namespace. This Kubernetes Pod will use the previously pushed image YOUR_DOMAIN/my-nginx.

First, you have to create the test Kubernetes namespace:

kubectl create namespace test

Create Docker Registry Secret

This is the most important step.

You have to create a Docker Secret which will be used for accessing the Docker registry from your Kubernetes cluster. To do this use the credentials from the previous step and create a Kubernetes Docker Secret in the test namespace:

kubectl create secret docker-registry regcred --docker-server=YOUR_DOMAIN --docker-username=admin --docker-password=registryPass -n test
💡
Note: It is very important that this Kubernetes Docker secret resource is created within the correct namespace!

Create Kubernetes Pod With Image From Docker Registry

After the Kubernetes secret containing the credentials for the Docker registry is created, you create a new Kubernetes Deployment (test-nginx.yaml) that uses your registry:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  namespace: test
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 1
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: YOUR_DOMAIN/my-nginx
        ports:
        - containerPort: 80
      imagePullSecrets:
        - name: regcred

If taking a close look at this file you will see that it contains three essential differences from a normal nginx deployment.

  1. image: YOUR_DOMAIN/my-nginx
  2. imagePullSecrets:
    - name: regred

Both changes are important in order to use the Docker registry. The first one selects the image to use in the pod (registry URL as a prefix + image name). The second option sets which Docker secret will be used to pull the image. If you changed credentials or names you have to update both values.

Now, deploy the Kubernetes Deployment in your Cluster:

kubectl apply -f test-nginx.yaml

Use kubectl describe podname -n test to see if the image will be pulled and the container gets started correctly. The output should look like this:

Pod Pull Image Events after deployment my-nginx Kubernetes Deployment

Closing Notes

Hopefully, this article gave you a good overview of how to set up a private Docker registry in your Kubernetes cluster.

Keep in mind that having a Private Docker registry is essential if deploying Docker services that are not Open Source

To have an easy repeatable procedure that you can run on any Kubernetes cluster I created a GitHub Gist containing all necessary files. If you already have a Traefik Ingress Controller running in your Kubernetes cluster you can simply download all files, adjust them to your needs, and then apply them by executing:

kubectl create namespace docker-registry
kubectl apply -f registry-pvc.yaml
helm repo add twuni https://helm.twun.io
helm repo update
helm search repo docker-registry
helm install -f values.yaml docker-registry --namespace docker-registry twuni/docker-registry
kubectl apply -f registry-ingressroute.yaml

I would love to hear your feedback about this tutorial. Furthermore, if you also have set up a Docker registry and use a different approach, please comment here and explain what you have done differently. Also, if you have any questions, please ask them in the comments. I try to answer them if possible.

Feel free to connect with me on Medium, LinkedIn, Twitter, and GitHub.