Container Registry
Panduan lengkap setup dan konfigurasi Docker Registry v2 sebagai alternatif Docker Hub untuk menyimpan dan mendistribusikan container images di Kubernetes.
Pengenalan
Container Registry adalah service untuk menyimpan, mengelola, dan mendistribusikan container images. Docker Registry v2 adalah implementasi lightweight dan simple yang cocok untuk self-hosted registry tanpa overhead kompleksitas enterprise solutions.
Mengapa Self-Hosted Registry?
Masalah Docker Hub:
- ❌ Rate limiting (100 pulls/6 jam untuk anonymous, 200 untuk authenticated)
- ❌ Public images (untuk free tier)
- ❌ Limited bandwidth
- ❌ Dependency pada external service
- ❌ Biaya untuk private repositories
Keuntungan Self-Hosted Docker Registry:
- ✅ No rate limits: Unlimited pulls
- ✅ Private by default: Full control atas images
- ✅ Network locality: Faster pulls dalam cluster
- ✅ Cost effective: No per-repo pricing
- ✅ Lightweight: Minimal resource requirements
- ✅ Simple: Easy to setup dan maintain
- ✅ Production ready: Proven, stable, widely used
Instalasi Docker Registry v2
Prasyarat
# Kubernetes cluster
kubectl version --short
# Persistent storage
kubectl get storageclass
Metode 1: Kubernetes Deployment (Production)
1. Create Namespace:
kubectl create namespace registry
2. Create PersistentVolumeClaim:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: registry-data
namespace: registry
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-path # Sesuaikan dengan storage class Anda
resources:
requests:
storage: 100Gi
3. Create Configuration (Optional):
apiVersion: v1
kind: ConfigMap
metadata:
name: registry-config
namespace: registry
data:
config.yml: |
version: 0.1
log:
level: info
fields:
service: registry
storage:
cache:
blobdescriptor: inmemory
filesystem:
rootdirectory: /var/lib/registry
delete:
enabled: true
http:
addr: :5000
headers:
X-Content-Type-Options: [nosniff]
debug:
addr: :5001
prometheus:
enabled: true
path: /metrics
health:
storagedriver:
enabled: true
interval: 10s
threshold: 3
4. Create Basic Auth Secret:
# Install htpasswd
apt-get install apache2-utils
# Create password file (user: admin, pass: yourpassword)
htpasswd -Bbn admin yourpassword > htpasswd
# Create Kubernetes secret
kubectl create secret generic registry-auth \
--from-file=htpasswd=htpasswd \
--namespace=registry
5. Create Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: docker-registry
namespace: registry
labels:
app: registry
spec:
replicas: 2
selector:
matchLabels:
app: registry
template:
metadata:
labels:
app: registry
spec:
containers:
- name: registry
image: registry:2.8
ports:
- containerPort: 5000
name: http
- containerPort: 5001
name: debug
env:
- name: REGISTRY_HTTP_SECRET
value: "changeme-random-secret-here"
- name: REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY
value: "/var/lib/registry"
- name: REGISTRY_STORAGE_DELETE_ENABLED
value: "true"
- name: REGISTRY_AUTH
value: "htpasswd"
- name: REGISTRY_AUTH_HTPASSWD_REALM
value: "Registry Realm"
- name: REGISTRY_AUTH_HTPASSWD_PATH
value: "/auth/htpasswd"
- name: REGISTRY_HTTP_DEBUG_ADDR
value: ":5001"
- name: REGISTRY_HTTP_DEBUG_PROMETHEUS_ENABLED
value: "true"
volumeMounts:
- name: registry-data
mountPath: /var/lib/registry
- name: auth
mountPath: /auth
readOnly: true
- name: config
mountPath: /etc/docker/registry
readOnly: true
livenessProbe:
httpGet:
path: /
port: 5000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /
port: 5000
initialDelaySeconds: 5
periodSeconds: 5
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 1000m
memory: 1Gi
volumes:
- name: registry-data
persistentVolumeClaim:
claimName: registry-data
- name: auth
secret:
secretName: registry-auth
- name: config
configMap:
name: registry-config
6. Create Service:
apiVersion: v1
kind: Service
metadata:
name: docker-registry
namespace: registry
labels:
app: registry
spec:
type: ClusterIP
selector:
app: registry
ports:
- name: http
port: 5000
targetPort: 5000
- name: debug
port: 5001
targetPort: 5001
7. Create Ingress:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: registry-ingress
namespace: registry
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
nginx.ingress.kubernetes.io/proxy-body-size: "0"
nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
ingressClassName: nginx
tls:
- hosts:
- registry.example.com
secretName: registry-tls
rules:
- host: registry.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: docker-registry
port:
number: 5000
8. Deploy All:
kubectl apply -f pvc.yaml
kubectl apply -f configmap.yaml
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
kubectl apply -f ingress.yaml
# Check status
kubectl get pods -n registry
kubectl get svc -n registry
kubectl get ingress -n registry
Metode 2: Helm Installation
# Add Helm repository
helm repo add twuni https://helm.twun.io
helm repo update
# Install with custom values
helm install docker-registry twuni/docker-registry \
--namespace registry \
--create-namespace \
--set persistence.enabled=true \
--set persistence.size=100Gi \
--set ingress.enabled=true \
--set ingress.hosts[0]=registry.example.com \
--set ingress.annotations."cert-manager\.io/cluster-issuer"=letsencrypt-prod \
--set secrets.htpasswd="admin:$2y$05$..." # htpasswd output
Custom Helm values.yaml:
replicaCount: 2
image:
repository: registry
tag: 2.8.3
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 5000
ingress:
enabled: true
className: nginx
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
nginx.ingress.kubernetes.io/proxy-body-size: "0"
hosts:
- registry.example.com
tls:
- secretName: registry-tls
hosts:
- registry.example.com
persistence:
enabled: true
accessMode: ReadWriteOnce
size: 100Gi
storageClass: "local-path"
secrets:
htpasswd: "admin:$2y$05$..."
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 1000m
memory: 1Gi
# Enable garbage collection
garbageCollect:
enabled: true
schedule: "0 1 * * *"
# Metrics
metrics:
enabled: true
port: 5001
serviceMonitor:
enabled: true
Install:
helm install docker-registry twuni/docker-registry \
--namespace registry \
--create-namespace \
-f values.yaml
Verifikasi Instalasi
# Check pods
kubectl get pods -n registry
# Check service
kubectl get svc -n registry
# Test registry endpoint
curl https://registry.example.com/v2/
# Expected output:
# {}
Penggunaan Registry
1. Login ke Registry
# Login
docker login registry.example.com
Username: admin
Password: yourpassword
# Verify login
cat ~/.docker/config.json
2. Tag dan Push Image
# Tag existing image
docker tag myapp:latest registry.example.com/myapp:latest
docker tag myapp:latest registry.example.com/myapp:v1.0.0
# Push image
docker push registry.example.com/myapp:latest
docker push registry.example.com/myapp:v1.0.0
3. Pull Image
# Pull dari registry
docker pull registry.example.com/myapp:latest
4. List Images (via API)
# List repositories
curl -u admin:yourpassword https://registry.example.com/v2/_catalog
# List tags untuk repository
curl -u admin:yourpassword https://registry.example.com/v2/myapp/tags/list
# Get manifest
curl -u admin:yourpassword https://registry.example.com/v2/myapp/manifests/latest
Kubernetes Integration
1. Create Image Pull Secret
# Method 1: kubectl create
kubectl create secret docker-registry regcred \
--docker-server=registry.example.com \
--docker-username=admin \
--docker-password=yourpassword \
--namespace=production
# Method 2: via YAML
kubectl apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
name: regcred
namespace: production
type: kubernetes.io/dockerconfigjson
data:
.dockerconfigjson: $(echo -n '{"auths":{"registry.example.com":{"username":"admin","password":"yourpassword","auth":"'$(echo -n admin:yourpassword | base64)'"}}}' | base64 -w 0)
EOF
2. Use in Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
imagePullSecrets:
- name: regcred
containers:
- name: myapp
image: registry.example.com/myapp:v1.0.0
ports:
- containerPort: 8080
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
3. Default ServiceAccount dengan ImagePullSecrets
# Patch default ServiceAccount
kubectl patch serviceaccount default \
-n production \
-p '{"imagePullSecrets": [{"name": "regcred"}]}'
# Sekarang semua pods di namespace production otomatis bisa pull images
CI/CD Integration
Gitea Actions - Build dan Push
name: Build and Push to Registry
on:
push:
branches: [main, develop]
tags: ['v*']
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to Registry
uses: docker/login-action@v2
with:
registry: registry.example.com
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v4
with:
images: registry.example.com/myapp
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha,prefix={{branch}}-
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=registry,ref=registry.example.com/myapp:buildcache
cache-to: type=registry,ref=registry.example.com/myapp:buildcache,mode=max
Multi-Architecture Build
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Build and push multi-arch
uses: docker/build-push-action@v4
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: registry.example.com/myapp:latest
Registry sebagai Docker Hub Mirror/Cache
Setup Pull-Through Cache
Konfigurasi registry sebagai proxy cache untuk Docker Hub:
1. Create Mirror Configuration:
apiVersion: v1
kind: ConfigMap
metadata:
name: registry-mirror-config
namespace: registry
data:
config.yml: |
version: 0.1
log:
level: info
storage:
filesystem:
rootdirectory: /var/lib/registry
delete:
enabled: true
http:
addr: :5000
proxy:
remoteurl: https://registry-1.docker.io
username: your-dockerhub-username
password: your-dockerhub-password
2. Deploy Mirror Registry:
apiVersion: apps/v1
kind: Deployment
metadata:
name: registry-mirror
namespace: registry
spec:
replicas: 2
selector:
matchLabels:
app: registry-mirror
template:
metadata:
labels:
app: registry-mirror
spec:
containers:
- name: registry
image: registry:2.8
ports:
- containerPort: 5000
volumeMounts:
- name: config
mountPath: /etc/docker/registry
- name: cache
mountPath: /var/lib/registry
volumes:
- name: config
configMap:
name: registry-mirror-config
- name: cache
emptyDir: {} # Atau PVC untuk persistent cache
3. Use Mirror:
# Pull via mirror
docker pull registry-mirror.example.com/library/nginx:latest
# Kubernetes node configuration
# /etc/docker/daemon.json
{
"registry-mirrors": ["https://registry-mirror.example.com"]
}
Management dan Maintenance
List All Images
# Get catalog
curl -u admin:password https://registry.example.com/v2/_catalog | jq
# Output:
# {
# "repositories": [
# "myapp",
# "frontend",
# "backend"
# ]
# }
# List tags for repository
curl -u admin:password https://registry.example.com/v2/myapp/tags/list | jq
Delete Image
# Get digest
IMAGE_DIGEST=$(curl -I -u admin:password \
-H "Accept: application/vnd.docker.distribution.manifest.v2+json" \
https://registry.example.com/v2/myapp/manifests/v1.0.0 \
| grep Docker-Content-Digest | awk '{print $2}' | tr -d '\r')
# Delete manifest
curl -u admin:password -X DELETE \
https://registry.example.com/v2/myapp/manifests/$IMAGE_DIGEST
# Run garbage collection (in pod)
kubectl exec -n registry deployment/docker-registry -- \
registry garbage-collect /etc/docker/registry/config.yml
Garbage Collection (Automated)
apiVersion: batch/v1
kind: CronJob
metadata:
name: registry-garbage-collect
namespace: registry
spec:
schedule: "0 2 * * 0" # Weekly at 2 AM Sunday
jobTemplate:
spec:
template:
spec:
containers:
- name: garbage-collect
image: registry:2.8
command:
- /bin/sh
- -c
- registry garbage-collect /etc/docker/registry/config.yml
volumeMounts:
- name: registry-data
mountPath: /var/lib/registry
- name: config
mountPath: /etc/docker/registry
volumes:
- name: registry-data
persistentVolumeClaim:
claimName: registry-data
- name: config
configMap:
name: registry-config
restartPolicy: OnFailure
Monitoring
Prometheus Metrics
Registry expose metrics di port 5001 (debug port).
ServiceMonitor:
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: docker-registry
namespace: registry
spec:
selector:
matchLabels:
app: registry
endpoints:
- port: debug
interval: 30s
path: /metrics
Key Metrics:
# HTTP requests
registry_http_requests_total
# Storage actions
registry_storage_action_seconds_count
# In-flight requests
registry_http_in_flight_requests
# Response size
registry_http_response_size_bytes
Grafana Dashboard
Create custom dashboard atau import community dashboards.
Important Panels:
- Total requests/second
- Request duration
- Storage usage
- Error rate
- Active connections
Backup dan Restore
Backup Registry Data
# Create backup CronJob
apiVersion: batch/v1
kind: CronJob
metadata:
name: registry-backup
namespace: registry
spec:
schedule: "0 3 * * *" # Daily at 3 AM
jobTemplate:
spec:
template:
spec:
containers:
- name: backup
image: alpine:latest
command:
- /bin/sh
- -c
- |
apk add --no-cache tar gzip
cd /var/lib/registry
tar czf /backup/registry-$(date +%Y%m%d).tar.gz docker
volumeMounts:
- name: registry-data
mountPath: /var/lib/registry
readOnly: true
- name: backup
mountPath: /backup
volumes:
- name: registry-data
persistentVolumeClaim:
claimName: registry-data
- name: backup
persistentVolumeClaim:
claimName: backup-pvc
restartPolicy: OnFailure
Manual Backup
# Backup PVC data
kubectl exec -n registry deployment/docker-registry -- \
tar czf - /var/lib/registry > registry-backup.tar.gz
# Or use kubectl cp
kubectl cp registry/docker-registry-pod:/var/lib/registry ./registry-backup
Restore
# Restore dari backup
kubectl cp ./registry-backup registry/docker-registry-pod:/var/lib/registry
# Or via tar
cat registry-backup.tar.gz | kubectl exec -i -n registry deployment/docker-registry -- \
tar xzf - -C /
Security Best Practices
1. Always Use HTTPS/TLS
# Ingress dengan cert-manager
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
tls:
- hosts:
- registry.example.com
secretName: registry-tls
2. Enable Authentication
# htpasswd untuk basic auth
htpasswd -Bbn admin secure-password > htpasswd
kubectl create secret generic registry-auth --from-file=htpasswd -n registry
3. Network Policies
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: registry-netpol
namespace: registry
spec:
podSelector:
matchLabels:
app: registry
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector: {}
ports:
- protocol: TCP
port: 5000
egress:
- to:
- namespaceSelector: {}
- to: # Allow DNS
- namespaceSelector:
matchLabels:
name: kube-system
ports:
- protocol: UDP
port: 53
4. Resource Limits
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 1000m
memory: 1Gi
5. Read-Only Root Filesystem
securityContext:
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 1000
6. Regular Updates
# Update registry image regularly
kubectl set image deployment/docker-registry \
registry=registry:2.8.3 -n registry
Troubleshooting
Issue 1: Push Failed - Unauthorized
# Check credentials
docker login registry.example.com
# Verify secret
kubectl get secret regcred -n production -o yaml
# Test authentication
curl -u admin:password https://registry.example.com/v2/
Issue 2: Push Failed - Insufficient Storage
# Check PVC size
kubectl get pvc -n registry
# Check usage
kubectl exec -n registry deployment/docker-registry -- df -h /var/lib/registry
# Resize PVC (if storage class supports it)
kubectl patch pvc registry-data -n registry -p '{"spec":{"resources":{"requests":{"storage":"200Gi"}}}}'
Issue 3: Slow Push/Pull
# Check network
kubectl exec -n registry deployment/docker-registry -- ping registry.example.com
# Check resource usage
kubectl top pod -n registry
# Increase replicas
kubectl scale deployment docker-registry --replicas=3 -n registry
# Check ingress timeout
kubectl get ingress -n registry -o yaml | grep timeout
Issue 4: Image Not Found After Push
# Verify image was pushed
curl -u admin:password https://registry.example.com/v2/_catalog
# Check logs
kubectl logs -n registry deployment/docker-registry --tail=100
# Verify manifest
curl -u admin:password https://registry.example.com/v2/myapp/tags/list
Performance Optimization
1. Use SSD Storage
persistence:
storageClass: "fast-ssd" # Use SSD storage class
2. Enable HTTP/2
Registry v2 supports HTTP/2 by default via Ingress.
3. Increase Replicas
kubectl scale deployment docker-registry --replicas=3 -n registry
4. Add CDN/LoadBalancer
service:
type: LoadBalancer
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
5. Cache Layer
Use Redis for metadata caching:
cache:
blobdescriptor: redis
redis:
addr: redis:6379
password: ""
db: 0
dialtimeout: 10ms
readtimeout: 10ms
writetimeout: 10ms
pool:
maxidle: 16
maxactive: 64
idletimeout: 300s
Storage Backends
S3 Compatible Storage
storage:
s3:
accesskey: AKIAIOSFODNN7EXAMPLE
secretkey: wJalrXUtnFEMI/K7MDENG
region: us-east-1
bucket: my-registry-bucket
encrypt: true
secure: true
MinIO Backend
storage:
s3:
accesskey: minioadmin
secretkey: minioadmin
region: us-east-1
regionendpoint: http://minio:9000
bucket: registry
secure: false
Registry UI (Optional)
Untuk web UI, gunakan Docker Registry UI:
apiVersion: apps/v1
kind: Deployment
metadata:
name: registry-ui
namespace: registry
spec:
replicas: 1
selector:
matchLabels:
app: registry-ui
template:
metadata:
labels:
app: registry-ui
spec:
containers:
- name: ui
image: joxit/docker-registry-ui:latest
ports:
- containerPort: 80
env:
- name: REGISTRY_URL
value: "https://registry.example.com"
- name: REGISTRY_TITLE
value: "My Docker Registry"
- name: DELETE_IMAGES
value: "true"
- name: SHOW_CONTENT_DIGEST
value: "true"
---
apiVersion: v1
kind: Service
metadata:
name: registry-ui
namespace: registry
spec:
selector:
app: registry-ui
ports:
- port: 80
targetPort: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: registry-ui-ingress
namespace: registry
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
ingressClassName: nginx
tls:
- hosts:
- registry-ui.example.com
secretName: registry-ui-tls
rules:
- host: registry-ui.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: registry-ui
port:
number: 80
Kesimpulan
Docker Registry v2 adalah solusi lightweight, simple, dan reliable untuk self-hosted container registry:
Keuntungan
- ✅ Simple: Easy setup, minimal configuration
- ✅ Lightweight: Low resource usage
- ✅ Fast: Excellent performance
- ✅ Stable: Production-proven
- ✅ Cost-effective: No licensing fees
- ✅ Flexible: Support berbagai storage backends
Best For
- Small to medium teams
- Kubernetes clusters
- CI/CD pipelines
- Private image storage
- Registry mirrors/caches