When using a Kubernetes cluster, sooner or later you often use custom container images. You can store these publicly or host them on your own container registry.
In this tutorial, we will show you how to deploy a Docker Container Registry on a Kubernetes cluster and push your own container images to it.
For this tutorial, we assume that you are using Traefik within your Kubernetes cluster, see our Traefik for Kubernetes tutorial. One handy advantage of this is that you don't need to create a separate load balancer and that Traefik provides a Let's Encrypt certificate to secure the connection to your Docker Container Registry.
Step 1
To connect to your upcoming Docker Registry, you need a username and password. Generate a password with the command below, replacing 'username' with a username of your choice.
The addition 'tee registry.htpasswd' ensures that the output is saved in a file named registry.htpasswd in the current directory.
htpasswd -nB username | tee registry.htpasswd
You will now receive two prompts for a password and then in the output the username and the encrypted password:
New password: Re-type new password: username:$5y$03$zn9rxGHYZBrRY45OT..VDO2oOISfl561Q0kvdDyvuZtlrOKV6KXVe
Step 2
Create a namespace for your Docker Registry, for example:
kubectl create ns docker-registry
Step 3
Create a 'secret' based on the password you just generated. This will allow your Docker Registry to verify the username and password from the previous step when you connect to it.
kubectl create secret generic registry-auth-secret --from-file=users=registry.htpasswd -n docker-registry
Step 4
Create a .yaml deployment file for your Docker Registry:
nano registry-deployment.yaml
Give the file the content below. Replace registry.yourdomain.com with a subdomain of your choice. Please note that the domain must be included in your TransIP account, otherwise Traefik cannot assign a Let's Encrypt certificate to it.
# registry-deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: docker-registry-deployment namespace: docker-registry labels: app: docker-registry spec: replicas: 1 selector: matchLabels: app: docker-registry template: metadata: labels: app: docker-registry spec: volumes: - name: registry-pv-storage persistentVolumeClaim: claimName: registry-pv-claim containers: - name: docker-registry-container image: registry:2 ports: - name: docker-registry containerPort: 5000 volumeMounts: - mountPath: "/var/lib/registry" name: registry-pv-storage --- apiVersion: v1 kind: Service metadata: name: docker-registry namespace: docker-registry spec: ports: - name: http port: 5000 selector: app: docker-registry --- apiVersion: traefik.containo.us/v1alpha1 kind: Middleware metadata: name: registry-ingress-auth namespace: docker-registry spec: basicAuth: secret: registry-auth-secret removeHeader: true --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: registry-pv-claim namespace: docker-registry spec: accessModes: - ReadWriteOnce resources: requests: storage: 5Gi --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: docker-registry-ingress namespace: docker-registry annotations: ingressClassName: traefik traefik.ingress.kubernetes.io/router.tls.certresolver: myresolver traefik.ingress.kubernetes.io/router.middlewares: traefik-registry-ingress-auth@kubernetescrd spec: rules: - host: registry.jouwdomein.nl http: paths: - path: / pathType: Prefix backend: service: name: docker-registry port: number: 5000
Code explanation
- The first block of the code specifically addresses deployment. Refer to our manual 'Creating an object based deployment in Kubernetes' for detailed information on how a deployment through .yaml file is structured.
--- - kind: Service: Create a service for the Docker Registry.
- metadata.name: docker-registry: Name of the service.
- metadata.namespace: docker-registry: Namespace where the component is deployed. You'll notice this element appears in multiple sections of the .yaml file to identify the namespace for that specific component.
- spec.ports.name/port: Name and port number respectively that are exposed.
-
spec.selector.app: docker-registry: The pod to which this service routes traffic.
--- - kind: Middleware: Middleware is specific to Traefik and allows you to tweak traffic before it is sent to a service.
- metadata.name: registry-ingress-auth: Name of the Middleware.
- spec.basicAuth: Enable basic authentication.
- spec.basicAuth.secret: registry-auth-secret: The secret you created in step 3 for authentication with your Docker Registry.
-
spec.basicAuth.removeHeader: true: Remove the Authorization header after successful authentication.
--- - kind: PersistentVolumeClaim: A PersistentVolumeClaim (PVC) is simply a request for extra disk space that your deployment can use to write data to.
- metadata.name: registry-pv-claim: Name of the PersistentVolumeClaim.
- spec.accessMode: ReadWriteOnce: The PVC can be used by a single node (but by multiple pods on that node) with Read Write permissions.
-
spec.resources.requests:storage: 5Gi: The size of the PersistentVolumeClaim. In this example, 5 gibibytes. You are free to use G for gigabyte, see also this page for more information on gibibytes.
--- - kind: Ingress: An API object that manages external access to services in a cluster, usually HTTP.
- metadata.name: docker-registry-ingress: Name of the Ingress.
- metadata.annotations: kubernetes.io/ingress.class: traefik: Which Ingress controller should be used to route traffic. In this case, Traefik.
- metadata.annotations: traefik.ingress.kubernetes.io/router.tls.certresolver: myresolver: Configures a (Let's Encrypt) certresolver for HTTPS traffic.
- metadata.annotations: traefik.ingress.kubernetes.io/router.middlewares: traefik-registry-ingress-auth@kubernetescrd: Adds a Middleware object to the router to handle authentication.
- spec.rules.host: registry.yourdomain.com: The (sub)domain on which your Docker Registry is accessible.
- spec.rules.http.paths.path.backend.service.name: docker-registry: The service to which traffic is routed.
- spec.rules.http.paths.path.backend.service.port: 5000: The port to which traffic is routed.
Save the changes and close the file (ctrl + x > y > enter).
Step 5
Deploy your Docker Registry with the command:
kubectl apply -f registry-deployment.yaml
Alternatively, you can remove the metadata.namespace from the various components in the .yaml file and use the command below (using -n docker-registry specifies the namespace the components are to be created in). This simplifies the .yaml file a little and makes it easier to deploy it across namespaces. On the other hand, having it in the .yaml file makes the .yaml file more complete and easier to troubleshoot.
kubectl apply -f registry-deployment.yaml -n docker-registry
Step 6
Your Docker Registry is now deployed! It may take up to a minute or two to get a public IP address. Check the status of your services to retrieve the public IP address with the command:
kubectl get svc -n docker-registry
Set the public IP address in your DNS records for the subdomain from step 4. After this, it may take some time (varying from 5 minutes to a maximum of one or two hours) for Traefik to assign a Let's Encrypt certificate to your subdomain.
Pushing containers to your Docker Registry
To push a containerized application to your Docker Registry, you need to follow these three steps:
Connect to your own registry using the username and password from step 1 of this article:
docker login registry.yourdomain.com
Tag your container image with the Harbor registry URL:
docker tag my-image registry.yourdomain.com/my-image
Push your container image to the Harbor registry:
docker push registry.yourdomain.com/my-image
We explain how to deploy your containerized application in a Kubernetes cluster in our guide 'Deploying your containerized image in a Kubernetes cluster'.