Deploy Private Registry on Kubernetes

Setting up a private Docker registry on Kubernetes provides secure, self-hosted container image storage for your development and production workflows. This comprehensive guide walks through deploying a fully configured private registry with TLS encryption, HTTP authentication, persistent storage, and seamless cluster integration.

A private registry offers several advantages over public registries: enhanced security for proprietary images, faster image pulls within your network, compliance with organizational policies, and complete control over your container artifacts. This setup uses the official Docker Registry v2 image with htpasswd authentication and self-signed certificates for secure access.

Prerequisites

Before starting this deployment, ensure you have:

  • A running Kubernetes cluster with ingress controller (this guide uses Cilium)
  • kubectl configured to access your cluster
  • OpenSSL for certificate generation
  • Docker installed on your local machine for testing
  • Basic understanding of Kubernetes resources and YAML manifests

For related Kubernetes setup guides, see our complete Kubernetes cluster setup with Cilium networking.

Step 1: Create Namespace and Storage

Create Registry Namespace

Start by creating a dedicated namespace for the registry components:

kubectl create namespace registry

Configure Persistent Storage

Create a persistent volume claim to store registry data. This example uses a 20GB volume:

# registry-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: registry-pvc
  namespace: registry
spec:
  accessModes:
    - ReadWriteOnce
  volumeMode: Filesystem
  resources:
    requests:
      storage: 20Gi
  storageClassName: nfs-client  # Adjust to your storage class

Note: Replace nfs-client with your cluster’s available storage class. Use kubectl get storageclass to list available options.

Step 2: Generate TLS Certificates

Create Self-Signed Certificate

Generate a self-signed certificate for secure HTTPS access. Replace registry.ak8s.net with your desired domain. For detailed certificate management and verification commands, see our OpenSSL certificate management guide:

openssl req -x509 -nodes -days 365 \
  -addext "subjectAltName = DNS:registry.ak8s.net" \
  -newkey rsa:2048 \
  -keyout registry.ak8s.net.key \
  -out registry.ak8s.net.crt \
  -subj "/CN=registry.ak8s.net"

This command creates:

  • registry.ak8s.net.crt: Public certificate
  • registry.ak8s.net.key: Private key

Create TLS Secret

Generate the Kubernetes TLS secret YAML:

kubectl -n registry create secret tls registry-tls \
  --cert=./registry.ak8s.net.crt \
  --key=./registry.ak8s.net.key \
  --dry-run=client -o yaml > registry-tls.yaml

Step 3: Configure Authentication

Generate htpasswd File

Create authentication credentials using Docker. This script generates a secure htpasswd file:

#!/bin/bash
export REGISTRY_USER='username'
export REGISTRY_PASS='password'
export DESTINATION_FOLDER=./registry-creds
   
# Backup credentials to local files
mkdir -p ${DESTINATION_FOLDER}
echo ${REGISTRY_USER} >> ${DESTINATION_FOLDER}/registry-user.txt
echo ${REGISTRY_PASS} >> ${DESTINATION_FOLDER}/registry-pass.txt
   	
# Generate htpasswd file using Docker
docker run --rm xmartlabs/htpasswd ${REGISTRY_USER} ${REGISTRY_PASS} > htpasswd
      
# Clean up environment variables
unset REGISTRY_USER REGISTRY_PASS DESTINATION_FOLDER

Security Note: Store these credentials securely and use strong passwords for production deployments.

Create Authentication Secret

Generate the htpasswd secret YAML:

kubectl -n registry create secret generic registry-htpasswd \
  --from-file=htpasswd \
  --dry-run=client -o yaml > registry-htpasswd.yaml

Step 4: Deploy Registry Components

Registry Deployment

Create the main registry deployment with authentication and persistent storage:

# registry-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
    name: registry
    namespace: registry
spec:
    replicas: 1
    selector:
        matchLabels:
            app: registry
    template:
        metadata:
            labels:
                app: registry
        spec:
            containers:
                - name: registry
                  image: registry:2
                  ports:
                      - name: registry
                        containerPort: 5000
                  volumeMounts:
                      - name: image-store
                        mountPath: /var/lib/registry
                      - name: auth
                        mountPath: /auth
                        readOnly: true
                  env:
                      - name: REGISTRY_AUTH
                        value: "htpasswd"
                      - name: REGISTRY_AUTH_HTPASSWD_REALM
                        value: "Registry Realm"
                      - name: REGISTRY_AUTH_HTPASSWD_PATH
                        value: "/auth/htpasswd"
            volumes:
                - name: image-store
                  persistentVolumeClaim:
                      claimName: registry-pvc
                - name: auth
                  secret:
                      secretName: registry-htpasswd

Registry Service

Create a service to expose the registry deployment:

# registry-service.yaml
apiVersion: v1
kind: Service
metadata:
    name: registry-svc
    namespace: registry
spec:
    selector:
        app: registry
    ports:
        - protocol: TCP
          port: 5000
          targetPort: 5000

Registry Ingress

Configure ingress for external access with TLS termination. For comprehensive nginx configuration patterns and SSL best practices, refer to our nginx reverse proxy configuration guide:

# registry-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
    name: registry-ingress
    namespace: registry
    annotations:
        nginx.ingress.kubernetes.io/proxy-body-size: 16m
spec:
    ingressClassName: cilium  # Adjust to your ingress class
    tls:
        - hosts:
              - registry.ak8s.net
          secretName: registry-tls
    rules:
        - host: registry.ak8s.net
          http:
              paths:
                  - pathType: Prefix
                    path: "/"
                    backend:
                        service:
                            name: registry-svc
                            port:
                                number: 5000

Step 5: Deploy All Components

Apply all configuration files to your cluster:

kubectl apply -f registry-htpasswd.yaml \
              -f registry-pvc.yaml \
              -f registry-tls.yaml \
              -f registry-deployment.yaml \
              -f registry-service.yaml \
              -f registry-ingress.yaml

Verify the deployment:

# Check all registry resources
kubectl -n registry get all
 
# Get ingress IP address
kubectl -n registry get ingress

Step 6: Configure DNS and Certificate Trust

Update DNS Resolution

Add the registry domain to your /etc/hosts file on all nodes and client machines:

# Get the ingress IP address
REGISTRY_IP=$(kubectl -n registry get ingress registry-ingress -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
 
# Add to /etc/hosts
echo "${REGISTRY_IP} registry.ak8s.net" | sudo tee -a /etc/hosts

Trust the Certificate

On each Kubernetes node and client machine, trust the self-signed certificate:

# For Arch Linux nodes
sudo trust anchor --store registry.ak8s.net.crt
 
# Restart container runtime
sudo systemctl restart containerd

For other distributions, copy the certificate to the appropriate trust store and update the certificate cache.

Step 7: Configure Cluster Access

Update CoreDNS

To enable registry access from within the cluster, update the CoreDNS configuration:

kubectl -n kube-system edit configmap coredns

Add the registry entry to the Corefile:

data:
  Corefile: |
    .:53 {
        # ... existing configuration ...
        hosts {
          192.168.1.100  registry.ak8s.net  # Replace with your ingress IP
          fallthrough
        }
        # ... rest of configuration ...
    }

Restart CoreDNS to apply changes:

kubectl -n kube-system rollout restart deployment coredns

Create Image Pull Secret

Create a secret for authenticating with the private registry from within the cluster:

kubectl create secret docker-registry regcred \
  --docker-server=registry.ak8s.net \
  --docker-username=username \
  --docker-password=password

Step 8: Testing the Registry

Build and Push Test Image

Create a simple test image to verify the registry:

# Dockerfile
FROM archlinux/archlinux
RUN pacman -Syu --noconfirm figlet
ENTRYPOINT ["figlet", "Hello, World!"]

Build and push the test image:

# Build the image
docker build . -t registry.ak8s.net/test_image:latest
 
# Login to the registry
docker login registry.ak8s.net
 
# Push the image
docker push registry.ak8s.net/test_image:latest

Test Cluster Access

Create a pod to test pulling from the private registry:

# test-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: private-reg-test
  namespace: default
spec:
  containers:
  - name: private-reg-container
    image: registry.ak8s.net/test_image:latest
  imagePullSecrets:
  - name: regcred

Apply and check the pod:

kubectl apply -f test-pod.yaml
kubectl logs private-reg-test

If successful, you should see “Hello, World!” in ASCII art.

Security Considerations

Production Deployment Best Practices

For production environments, consider these security enhancements:

  1. Use Valid TLS Certificates: Replace self-signed certificates with certificates from a trusted CA
  2. Implement RBAC: Configure role-based access control for registry operations
  3. Regular Security Updates: Keep the registry image updated to the latest version
  4. Network Policies: Implement Kubernetes network policies to restrict registry access
  5. Vulnerability Scanning: Integrate image vulnerability scanning in your CI/CD pipeline

Authentication Alternatives

While htpasswd provides basic authentication, consider these alternatives for enhanced security:

  • OAuth Integration: Configure OAuth providers for centralized authentication
  • LDAP/Active Directory: Integrate with existing directory services
  • Token-based Authentication: Implement JWT tokens for API access
  • Mutual TLS: Use client certificates for enhanced security

Troubleshooting

Common Issues and Solutions

Certificate Trust Issues:

  • Ensure the certificate is properly installed on all nodes
  • Verify the certificate includes the correct SAN (Subject Alternative Name) using OpenSSL verification commands
  • Restart the container runtime after adding certificates

Authentication Failures:

  • Verify htpasswd file is correctly generated and base64 encoded
  • Check that the authentication secret is properly mounted in the deployment
  • Ensure credentials match those used in the htpasswd file

Network Connectivity:

  • Confirm DNS resolution is working within the cluster
  • Verify ingress controller is properly configured
  • Check that firewall rules allow traffic on required ports

Storage Issues:

  • Ensure the storage class supports ReadWriteOnce access mode
  • Verify sufficient storage capacity is available
  • Check that the persistent volume is properly bound

Monitoring and Maintenance

Monitor your private registry with these practices:

  • Resource Usage: Monitor CPU, memory, and storage usage
  • Image Cleanup: Implement garbage collection for unused images
  • Backup Strategy: Regular backups of registry data and configuration
  • Access Logs: Review access logs for security auditing

References and Resources

Questions Answered in This Document

Q: How do I deploy a private Docker registry on Kubernetes? A: Deploy a private registry using the official registry:2 image with persistent storage, TLS encryption, and htpasswd authentication, configured through Kubernetes deployments, services, and ingress resources.

Q: How do I secure a private Docker registry with TLS certificates? A: Generate self-signed certificates using OpenSSL commands with proper Subject Alternative Names, create Kubernetes TLS secrets, and configure ingress to terminate TLS connections with the certificates.

Q: How do I configure authentication for a private Docker registry? A: Use htpasswd-based authentication by generating credential files with Docker, creating Kubernetes secrets, and mounting them in the registry deployment with appropriate environment variables. For additional security options, see certificate-based authentication methods.

Q: How do I enable private registry access from within a Kubernetes cluster? A: Configure CoreDNS for internal DNS resolution, trust certificates on all nodes, create docker-registry secrets for authentication, and use imagePullSecrets in pod specifications. Ensure proper ingress configuration for internal routing.

Q: How do I troubleshoot private registry connectivity issues? A: Verify certificate trust, check DNS resolution, confirm authentication credentials, validate network policies, and ensure proper ingress configuration with correct backend services.

Q: What storage considerations are important for a private Docker registry? A: Use persistent volumes with ReadWriteOnce access mode, provision adequate storage capacity for image storage, implement backup strategies, and consider performance requirements for concurrent access.

Q: How do I test if my private Docker registry is working correctly? A: Build and push test images, verify authentication by logging in with Docker, create test pods that pull from the registry, and check logs for successful image pulls and container startup.

Q: What are the security best practices for private Docker registries in production? A: Use valid TLS certificates from trusted CAs, implement strong authentication mechanisms, configure network policies, keep registry software updated, and integrate vulnerability scanning into your workflow.