A hands-on implementation of a private Docker container registry deployed on Kubernetes with TLS security, authentication, persistent storage, and private image management.
This project demonstrates how to build a self-hosted container registry, push Docker images to it, and configure Kubernetes to securely pull and deploy private images.
| Technology | Purpose |
|---|---|
| Kubernetes | Container orchestration, Deployments, Services, Secrets, and Pod management |
| Docker | Building, tagging, authenticating, pushing, and managing container images |
| containerd | Container runtime used by Kubernetes nodes |
| Docker Registry | Self-hosted private container image repository |
| TLS / SSL | Secures communication between nodes and the private registry |
| OpenSSL | Generates self-signed TLS certificates |
| htpasswd | Provides username/password authentication for the registry |
| Kubernetes Secrets | Stores TLS credentials, authentication data, and registry credentials |
| Persistent Volume (PV) | Provides persistent storage for registry data |
| Persistent Volume Claim (PVC) | Requests and connects persistent storage to the registry |
| Linux / Ubuntu | Kubernetes node and registry environment |
| Bash | Used for cluster configuration and automation commands |
| Nginx | Sample container image used to test the private registry |
| kubectl | CLI used to manage and troubleshoot Kubernetes resources |
- Private Docker Registry with TLS encryption
- Username/password authentication using htpasswd
- Kubernetes Secrets for secure credential management
- Persistent storage using PV/PVC
- Docker image push and pull workflow
- Kubernetes deployment of private images
imagePullSecretsfor authenticated image pulls- Registry DNS configuration across cluster nodes
- Container image versioning using tags such as
v1andv2 - Kubernetes image version updates and rollouts
- Image and application verification using Docker,
kubectl, andcurl
Kubernetes Cluster
┌───────────────────────────────────────────┐
│ Master Node │
│ │
│ Kubernetes API / kubectl │
│ │ │
│ ▼ │
│ Registry Deployment │
│ │ │
│ ▼ │
│ Private Docker Registry │
│ my-registry:5000 │
│ │ │
│ ▼ │
│ Persistent Volume / Storage │
│ │
└──────────────────┬────────────────────────┘
│
Push / Pull Images
│
┌──────────────────▼────────────────────────┐
│ Worker Node │
│ │
│ Docker CLI │
│ │ │
│ nginx:latest │
│ │ │
│ Docker Tag │
│ ▼ │
│ my-registry:5000/mynginx:v1 │
│ │ │
│ ▼ │
│ Kubernetes Pod │
│ Nginx │
└───────────────────────────────────────────┘
k8s_private_registry/
└── demo-reg/
└── registry/
├── auth/
│ └── htpasswd
├── certs/
│ ├── tls.crt
│ └── tls.key
├── volume.yaml
├── deploy.yaml
└── service.yaml
volume.yaml— Defines persistent storage for the registry.deploy.yaml— Deploys the private Docker registry.service.yaml— Exposes the registry inside the Kubernetes cluster.auth/htpasswd— Stores registry authentication credentials.certs/tls.crt— TLS certificate.certs/tls.key— TLS private key.
git clone https://github.com/1-DARK/k8s_private_registry.git
cd k8s_private_registry
cd demo-reg
cd registry
alias k=kubectlGenerate a self-signed certificate for the registry:
openssl req -x509 -newkey rsa:4096 -days 365 -nodes -sha256 \
-keyout certs/tls.key \
-out certs/tls.crt \
-subj "/CN=my-registry" \
-addext "subjectAltName = DNS:my-registry"Verify:
ls certsExpected:
tls.crt
tls.key
Install Docker:
sudo apt update
sudo apt install -y docker.ioGenerate the htpasswd authentication file:
sudo docker run --entrypoint htpasswd \
docker.io/library/httpd:2 \
-Bbn myuser mypasswd > auth/htpasswdVerify:
cat auth/htpasswdExample credentials:
Username: myuser
Password: mypasswd
For production environments, use stronger credentials and avoid storing plaintext passwords in shell history or public repositories.
kubectl create secret tls certs-secret \
--cert=certs/tls.crt \
--key=certs/tls.keykubectl create secret generic auth-secret \
--from-file=auth/htpasswdCheck:
kubectl get secretsCreate the registry storage directory:
mkdir -p /home/ubuntu/reposThe registry will persist image data under:
/home/ubuntu/repos/docker/registry/v2/
Apply the Persistent Volume configuration:
kubectl apply -f volume.yamlDeploy the registry:
kubectl apply -f deploy.yamlCreate the registry Service:
kubectl apply -f service.yamlVerify:
kubectl get pv
kubectl get pvc
kubectl get pods
kubectl get svcSet the registry name:
export REGISTRY_NAME="my-registry"Find the Service IP:
kubectl get svcAdd the registry name to /etc/hosts on both Master and Worker nodes:
sudo vi /etc/hostsAdd:
<SERVICE-IP> my-registry
For example:
10.96.100.50 my-registry
Test:
ping my-registryOn the Master Node, display the certificate:
cat certs/tls.crtCopy the certificate to:
/usr/local/share/ca-certificates/tls.crt
Then run:
sudo update-ca-certificatesPerform the same configuration on the Worker Node.
On the required nodes:
sudo mkdir -p /etc/docker/certs.d/my-registry:5000Copy the certificate:
sudo cp /usr/local/share/ca-certificates/tls.crt \
/etc/docker/certs.d/my-registry:5000/tls.crtVerify:
ls /etc/docker/certs.d/my-registry:5000/Expected:
tls.crt
On the Worker Node:
docker login my-registry:5000 \
-u myuser \
-p mypasswdExpected:
Login Succeeded
Pull Nginx:
sudo docker pull nginxTag the image:
sudo docker tag nginx:latest \
my-registry:5000/mynginx:v1Push the image:
sudo docker push my-registry:5000/mynginx:v1Verify:
sudo docker imagesThe image should now be available as:
my-registry:5000/mynginx:v1
Check the registry storage:
cd /home/ubuntu/repos/docker/registry/v2/repositories/mynginx
ls -lrtYou can also inspect the registry Pod:
kubectl get podskubectl exec -it <registry-pod-name> -- shInside the Pod:
cd /var/lib/registry/docker/registry/v2/repositories/mynginx
ls -lrtExit:
exitCreate a registry authentication secret:
kubectl create secret docker-registry nginx-secret \
--docker-server=my-registry:5000 \
--docker-username=myuser \
--docker-password=mypasswdVerify:
kubectl get secretsThis allows Kubernetes to authenticate with the private registry when pulling images.
Restart containerd on the cluster nodes:
sudo systemctl restart containerdVerify:
sudo systemctl status containerdConfigure the Deployment to use the private registry:
spec:
containers:
- name: nginx
image: my-registry:5000/mynginx:v1
imagePullSecrets:
- name: nginx-secretApply:
kubectl apply -f new.yamlCheck:
kubectl get podsGet the Pod IP:
kubectl get pods -o wideTest from the Worker Node:
curl <POD-IP>The Nginx response confirms that the private image was successfully pulled and deployed.
Create a second version:
sudo docker tag nginx:latest \
my-registry:5000/mynginx:v2Push:
sudo docker push my-registry:5000/mynginx:v2Now the registry contains:
my-registry:5000/mynginx:v1
my-registry:5000/mynginx:v2
Change:
image: my-registry:5000/mynginx:v1to:
image: my-registry:5000/mynginx:v2Apply:
kubectl apply -f new.yamlCheck:
kubectl get podsVerify the image:
kubectl describe pod <pod-name>Or:
kubectl get pod <pod-name> \
-o jsonpath='{.spec.containers[0].image}'Expected:
my-registry:5000/mynginx:v2
Private Registry
│
┌─────────────┴─────────────┐
│ │
Security Persistence
│ │
TLS + htpasswd PV/PVC
│ │
└─────────────┬─────────────┘
│
Kubernetes
│
imagePullSecrets
│
▼
Private Image
│
▼
Nginx Pod
By completing this project, you build a working workflow where:
Docker Image
│
▼
Docker Tag
│
▼
Private Registry
│
│ Authentication + TLS
▼
Kubernetes
│
│ imagePullSecrets
▼
Private Image Pull
│
▼
Nginx Pod
This project provides practical experience with private container registries, Kubernetes storage, secrets, TLS security, container runtimes, and private image deployments.