Docker Registry Private Setup
A private Docker registry allows your organization to store, manage, and distribute container images securely without relying on public registries. This comprehensive guide covers installing, configuring, and maintaining a Docker Registry instance with authentication, TLS encryption, storage backends, and garbage collection. By hosting your own registry, you gain complete control over image distribution, improve deployment speed with local caching, and eliminate external dependencies for CI/CD pipelines.
Table of Contents
- Understanding Docker Registry
- Installing Docker Registry
- Configuring TLS/SSL Encryption
- Implementing Authentication
- Storage Backend Configuration
- Setting Up Nginx Frontend
- Managing Images and Garbage Collection
- High Availability Registry
- Monitoring and Maintenance
- Conclusion
Understanding Docker Registry
Docker Registry is the official Docker image distribution system. The registry:2 image provides a stateless, highly scalable application for managing and distributing images. Understanding its architecture is essential for production deployments.
Registry components:
- Registry API server: Handles image push/pull operations
- Storage driver: Persists image layers to filesystem, cloud storage, or object storage
- Authentication layer: Verifies client identity
- Garbage collection: Removes unreferenced blobs and layers
- Notification system: Emits events on image operations
# Pull official registry:2 image
docker pull registry:2
# Verify image
docker images | grep registry
# Check registry version
docker run --rm registry:2 /bin/registry --version
Key concepts:
- Blobs: Individual image layers and configurations
- Manifests: JSON documents describing image structure
- Repositories: Collections of related image tags
- Tags: Human-readable references to specific image versions
Installing Docker Registry
Deploy a registry instance with persistent storage for development and testing environments.
Basic registry setup:
# Create data directory with proper permissions
sudo mkdir -p /opt/registry/data
sudo chown -R 1000:1000 /opt/registry/data
sudo chmod 755 /opt/registry/data
# Run registry container
docker run -d \
--name registry \
--restart always \
-p 5000:5000 \
-v /opt/registry/data:/var/lib/registry \
registry:2
# Verify registry is running
docker ps | grep registry
# Test registry health
curl http://localhost:5000/v2/
Configure environment variables for registry:
# Create environment file
cat > /opt/registry/registry.env <<EOF
REGISTRY_LOG_LEVEL=info
REGISTRY_STORAGE_DELETE_ENABLED=true
REGISTRY_STORAGE_CACHE_BLOBDESCRIPTOR=inmemory
REGISTRY_HTTP_ADDR=0.0.0.0:5000
REGISTRY_HTTP_RELATIVEURLS=true
EOF
# Run with environment file
docker run -d \
--name registry \
--restart always \
-p 5000:5000 \
--env-file /opt/registry/registry.env \
-v /opt/registry/data:/var/lib/registry \
registry:2
# Verify environment applied
docker exec registry env | grep REGISTRY
Using docker-compose for registry:
# Create docker-compose.yml
cat > /opt/registry/docker-compose.yml <<EOF
version: '3.9'
services:
registry:
image: registry:2
container_name: docker-registry
restart: always
ports:
- "5000:5000"
volumes:
- /opt/registry/data:/var/lib/registry
- /opt/registry/config.yml:/etc/docker/registry/config.yml
environment:
REGISTRY_LOG_LEVEL: info
REGISTRY_STORAGE_DELETE_ENABLED: "true"
EOF
# Start registry
docker-compose -f /opt/registry/docker-compose.yml up -d
# Check logs
docker-compose -f /opt/registry/docker-compose.yml logs -f registry
Configuring TLS/SSL Encryption
TLS encryption is essential for production deployments to protect image transmission and credentials.
Generate self-signed certificates (development only):
# Create certificate directory
sudo mkdir -p /opt/registry/certs
cd /opt/registry/certs
# Generate private key
openssl genrsa -out domain.key 2048
# Generate certificate (valid 365 days)
openssl req -new \
-x509 \
-key domain.key \
-out domain.crt \
-days 365 \
-subj "/CN=registry.example.com"
# Verify certificate
openssl x509 -in domain.crt -text -noout
Generate CA-signed certificates (production):
# Create private key
openssl genrsa -out /opt/registry/certs/domain.key 2048
# Create certificate signing request
openssl req -new \
-key /opt/registry/certs/domain.key \
-out /opt/registry/certs/domain.csr \
-subj "/CN=registry.example.com" \
-addext "subjectAltName=DNS:registry.example.com,DNS:*.registry.example.com,IP:192.168.1.100"
# Submit CSR to CA and receive domain.crt (obtained from your certificate provider)
# Verify certificate chain
openssl verify -CAfile ca.crt domain.crt
Configure registry with TLS:
# Set certificate permissions
sudo chmod 600 /opt/registry/certs/domain.key
sudo chmod 644 /opt/registry/certs/domain.crt
# Run registry with TLS
docker run -d \
--name registry \
--restart always \
-p 443:5000 \
-v /opt/registry/data:/var/lib/registry \
-v /opt/registry/certs:/certs \
-e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt \
-e REGISTRY_HTTP_TLS_KEY=/certs/domain.key \
registry:2
# Verify HTTPS
curl --cacert /opt/registry/certs/domain.crt https://registry.example.com/v2/
Configure client to trust self-signed certificate:
# Copy certificate to Docker daemon config
sudo mkdir -p /etc/docker/certs.d/registry.example.com:443
sudo cp /opt/registry/certs/domain.crt \
/etc/docker/certs.d/registry.example.com:443/ca.crt
# Reload Docker daemon
sudo systemctl reload docker
# Test pull/push
docker pull registry.example.com/myimage:latest
Implementing Authentication
Secure your registry with authentication using htpasswd or other methods.
Basic htpasswd authentication:
# Create password file
mkdir -p /opt/registry/auth
docker run --rm \
--entrypoint htpasswd \
registry:2 -Bbc /dev/stdout admin password123 > /opt/registry/auth/htpasswd
# Set proper permissions
sudo chmod 600 /opt/registry/auth/htpasswd
# View password file
sudo cat /opt/registry/auth/htpasswd
# Add another user
docker run --rm \
--entrypoint htpasswd \
-v /opt/registry/auth:/auth \
registry:2 -Bbc /auth/htpasswd developer mypassword
Run registry with authentication:
# Stop previous registry
docker rm -f registry
# Run with auth
docker run -d \
--name registry \
--restart always \
-p 5000:5000 \
-v /opt/registry/data:/var/lib/registry \
-v /opt/registry/auth:/auth \
-v /opt/registry/certs:/certs \
-e REGISTRY_AUTH=htpasswd \
-e REGISTRY_AUTH_HTPASSWD_REALM="Registry Realm" \
-e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
-e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt \
-e REGISTRY_HTTP_TLS_KEY=/certs/domain.key \
registry:2
# Test authentication
docker login -u admin -p password123 registry.example.com
# Push image
docker tag myimage:latest registry.example.com/myimage:latest
docker push registry.example.com/myimage:latest
# View login credentials
cat ~/.docker/config.json
Token-based authentication (advanced):
# Create auth server configuration
cat > /opt/registry/auth-config.json <<EOF
{
"server": "https://auth.example.com/token",
"issuer": "myissuer",
"rootcertbundle": "/certs/ca.crt"
}
EOF
# Run registry with token auth
docker run -d \
--name registry \
--restart always \
-p 5000:5000 \
-v /opt/registry/data:/var/lib/registry \
-v /opt/registry/certs:/certs \
-e REGISTRY_AUTH=token \
-e REGISTRY_AUTH_TOKEN_REALM=https://auth.example.com/token \
-e REGISTRY_AUTH_TOKEN_SERVICE="Docker registry" \
-e REGISTRY_AUTH_TOKEN_ISSUER=myissuer \
-e REGISTRY_AUTH_TOKEN_ROOTCERTBUNDLE=/certs/ca.crt \
registry:2
Storage Backend Configuration
Configure different storage backends based on your infrastructure requirements.
Local filesystem storage (default):
# Already configured in basic setup
# Storage is at /var/lib/registry in container
# Inspect storage
docker exec registry ls -la /var/lib/registry/docker/registry/v2/
AWS S3 storage backend:
# Create registry config with S3 backend
cat > /opt/registry/config.yml <<EOF
version: 0.1
log:
level: info
storage:
s3:
accesskey: AKIAIOSFODNN7EXAMPLE
secretkey: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
region: us-east-1
bucket: my-docker-registry
encrypt: true
secure: true
v4auth: true
rootdirectory: /docker-registry
http:
addr: :5000
relativeurls: true
EOF
# Run registry with S3
docker run -d \
--name registry \
--restart always \
-p 5000:5000 \
-v /opt/registry/config.yml:/etc/docker/registry/config.yml \
registry:2
# Verify S3 connectivity
docker logs registry | grep -i s3
Azure Blob Storage backend:
# Create registry config for Azure
cat > /opt/registry/config.yml <<EOF
version: 0.1
storage:
azure:
accountname: myaccount
accountkey: ACCOUNT_KEY_HERE
container: docker-registry
realm: core.windows.net
http:
addr: :5000
EOF
# Run registry with Azure
docker run -d \
--name registry \
--restart always \
-p 5000:5000 \
-v /opt/registry/config.yml:/etc/docker/registry/config.yml \
registry:2
Configure cache layer for performance:
# Create config with Redis cache
cat > /opt/registry/config.yml <<EOF
version: 0.1
storage:
filesystem:
rootdirectory: /var/lib/registry
cache:
blobdescriptor: redis
redis:
addr: redis:6379
db: 0
dialtimeout: 10ms
readtimeout: 10ms
writetimeout: 10ms
pool:
maxidle: 16
maxactive: 64
idletimeout: 300s
http:
addr: :5000
relativeurls: true
EOF
# Update docker-compose to include Redis
cat > /opt/registry/docker-compose.yml <<EOF
version: '3.9'
services:
redis:
image: redis:7-alpine
container_name: registry-redis
restart: always
ports:
- "6379:6379"
registry:
image: registry:2
container_name: docker-registry
restart: always
ports:
- "5000:5000"
depends_on:
- redis
volumes:
- /opt/registry/data:/var/lib/registry
- /opt/registry/config.yml:/etc/docker/registry/config.yml
environment:
REGISTRY_LOG_LEVEL: info
EOF
docker-compose -f /opt/registry/docker-compose.yml up -d
Setting Up Nginx Frontend
Nginx provides reverse proxy functionality, load balancing, and additional security for your registry.
Install and configure Nginx:
# Install Nginx
sudo apt-get update
sudo apt-get install -y nginx
# Create Nginx config for registry
sudo tee /etc/nginx/sites-available/registry > /dev/null <<EOF
upstream registry {
server localhost:5000;
}
server {
listen 443 ssl http2;
server_name registry.example.com;
ssl_certificate /opt/registry/certs/domain.crt;
ssl_certificate_key /opt/registry/certs/domain.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
client_max_body_size 0;
location / {
proxy_pass http://registry;
proxy_set_header Host \$http_host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
proxy_buffering off;
proxy_request_buffering off;
}
}
server {
listen 80;
server_name registry.example.com;
return 301 https://\$server_name\$request_uri;
}
EOF
# Enable site
sudo ln -sf /etc/nginx/sites-available/registry /etc/nginx/sites-enabled/registry
# Test Nginx config
sudo nginx -t
# Start Nginx
sudo systemctl start nginx
sudo systemctl enable nginx
Configure Nginx for caching and compression:
# Update Nginx config with caching
sudo tee /etc/nginx/sites-available/registry > /dev/null <<EOF
upstream registry {
server localhost:5000;
keepalive 32;
}
proxy_cache_path /var/cache/nginx/docker-registry levels=1:2 keys_zone=registry_cache:10m max_size=1g inactive=60d use_temp_path=off;
server {
listen 443 ssl http2;
server_name registry.example.com;
ssl_certificate /opt/registry/certs/domain.crt;
ssl_certificate_key /opt/registry/certs/domain.key;
gzip on;
gzip_types application/json;
client_max_body_size 0;
location /v2/ {
proxy_pass http://registry;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host \$http_host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
# Cache GET requests
proxy_cache registry_cache;
proxy_cache_key "\$scheme\$request_method\$host\$request_uri";
proxy_cache_valid 200 60d;
proxy_cache_valid 404 10m;
proxy_cache_bypass \$http_pragma \$http_authorization;
}
}
EOF
# Reload Nginx
sudo systemctl reload nginx
Managing Images and Garbage Collection
Efficiently manage images and free up disk space with garbage collection.
Manually trigger garbage collection:
# Run garbage collection
docker exec registry bin/registry garbage-collect /etc/docker/registry/config.yml
# Check disk usage before
df -h /opt/registry/data
# Run collection with verbose output
docker exec registry bin/registry garbage-collect -d /etc/docker/registry/config.yml
# Check disk usage after
df -h /opt/registry/data
Schedule regular garbage collection:
# Create cron job
sudo crontab -e
# Add line to run daily at 2 AM
0 2 * * * docker exec registry bin/registry garbage-collect /etc/docker/registry/config.yml >> /var/log/registry-gc.log 2>&1
# View scheduled jobs
sudo crontab -l
Delete specific images or tags:
# Delete image by manifest digest
curl -X DELETE http://localhost:5000/v2/myimage/manifests/sha256:abc123def456
# List all repositories
curl http://localhost:5000/v2/_catalog
# List tags for repository
curl http://localhost:5000/v2/myimage/tags/list
# Get manifest digest
curl -H "Accept: application/vnd.docker.distribution.manifest.v2+json" \
-X GET http://localhost:5000/v2/myimage/manifests/latest | grep -i docker-content-digest
# Delete old tag
curl -X DELETE \
http://localhost:5000/v2/myimage/manifests/sha256:abc123...
# Run cleanup after deletion
docker exec registry bin/registry garbage-collect /etc/docker/registry/config.yml
Implement retention policies:
# Create cleanup script
cat > /opt/registry/cleanup.sh <<'EOF'
#!/bin/bash
REGISTRY="http://localhost:5000"
KEEP_TAGS=5
for repo in $(curl -s $REGISTRY/v2/_catalog | jq -r '.repositories[]'); do
echo "Processing $repo..."
# Get all tags sorted by date
tags=$(curl -s $REGISTRY/v2/$repo/tags/list | jq -r '.tags[] // empty' | sort -r)
tag_count=0
for tag in $tags; do
tag_count=$((tag_count + 1))
if [ $tag_count -gt $KEEP_TAGS ]; then
# Get manifest digest
digest=$(curl -I -H "Accept: application/vnd.docker.distribution.manifest.v2+json" \
-s $REGISTRY/v2/$repo/manifests/$tag | grep -i docker-content-digest | cut -d' ' -f2 | tr -d '\r')
# Delete old tag
curl -X DELETE $REGISTRY/v2/$repo/manifests/$digest
echo "Deleted $repo:$tag"
fi
done
done
# Run garbage collection
docker exec registry bin/registry garbage-collect /etc/docker/registry/config.yml
EOF
chmod +x /opt/registry/cleanup.sh
# Run cleanup
/opt/registry/cleanup.sh
High Availability Registry
Configure multiple registry instances for high availability and load distribution.
Setup with load balancer:
# Create docker-compose for multiple registries
cat > /opt/registry/docker-compose-ha.yml <<EOF
version: '3.9'
services:
registry1:
image: registry:2
restart: always
environment:
REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /var/lib/registry
REGISTRY_LOG_LEVEL: info
volumes:
- /opt/registry/data:/var/lib/registry
- /opt/registry/config.yml:/etc/docker/registry/config.yml
networks:
- registry-net
registry2:
image: registry:2
restart: always
environment:
REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /var/lib/registry
REGISTRY_LOG_LEVEL: info
volumes:
- /opt/registry/data:/var/lib/registry
- /opt/registry/config.yml:/etc/docker/registry/config.yml
networks:
- registry-net
nginx-lb:
image: nginx:alpine
restart: always
ports:
- "5000:5000"
volumes:
- /opt/registry/nginx-lb.conf:/etc/nginx/nginx.conf:ro
depends_on:
- registry1
- registry2
networks:
- registry-net
networks:
registry-net:
EOF
# Create load balancer config
cat > /opt/registry/nginx-lb.conf <<EOF
events {
worker_connections 1024;
}
http {
upstream registry_backend {
server registry1:5000;
server registry2:5000;
}
server {
listen 5000;
client_max_body_size 0;
location / {
proxy_pass http://registry_backend;
proxy_set_header Host \$http_host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
proxy_buffering off;
proxy_request_buffering off;
}
}
}
EOF
# Start HA setup
docker-compose -f /opt/registry/docker-compose-ha.yml up -d
# Verify all services
docker-compose -f /opt/registry/docker-compose-ha.yml ps
Monitoring and Maintenance
Monitor registry health, track metrics, and maintain reliable operation.
Enable registry metrics:
# Create config with Prometheus metrics
cat > /opt/registry/config.yml <<EOF
version: 0.1
storage:
filesystem:
rootdirectory: /var/lib/registry
http:
addr: :5000
relativeurls: true
metrics:
enabled: true
prometheus:
namespace: registry
subsystem: storage
EOF
# Access metrics endpoint
curl http://localhost:5000/metrics
# Example metrics:
# registry_storage_action_seconds_bucket{action="blobstore_upload",le="+Inf"} 5
# registry_storage_action_seconds_sum{action="blobstore_upload"} 0.25
# registry_storage_action_seconds_count{action="blobstore_upload"} 1
Health check configuration:
# Test registry health
curl -s http://localhost:5000/v2/ && echo "Registry is healthy" || echo "Registry is unhealthy"
# Add health check to container
docker run -d \
--name registry \
--restart always \
-p 5000:5000 \
--health-cmd='curl -f http://localhost:5000/v2/ || exit 1' \
--health-interval=30s \
--health-timeout=10s \
--health-retries=3 \
-v /opt/registry/data:/var/lib/registry \
registry:2
# Check health status
docker inspect registry --format='{{.State.Health.Status}}'
Monitor disk usage and backups:
# Monitor disk usage
du -sh /opt/registry/data
# Set up disk usage alert
cat > /opt/registry/check-disk.sh <<'EOF'
#!/bin/bash
USAGE=$(df /opt/registry/data | awk 'NR==2 {print $5}' | sed 's/%//')
if [ $USAGE -gt 80 ]; then
echo "WARNING: Registry disk usage at $USAGE%"
# Run garbage collection
docker exec registry bin/registry garbage-collect /etc/docker/registry/config.yml
fi
EOF
chmod +x /opt/registry/check-disk.sh
# Run periodically
0 */4 * * * /opt/registry/check-disk.sh >> /var/log/registry-disk.log 2>&1
Conclusion
A private Docker registry is an essential component of professional container infrastructure. By implementing TLS encryption, authentication, and configurable storage backends, you create a secure, scalable image distribution system. Regular garbage collection, monitoring, and high availability configurations ensure long-term reliability. Whether you start with basic htpasswd authentication and local storage or scale to multi-instance deployments with S3 backends, the Docker Registry provides a foundation for container image management that supports your organization's growth. Invest time in proper configuration now to avoid operational headaches and security vulnerabilities as your container usage expands.


