Skip to main content

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

Resources