A private Docker registry is essential for DevOps teams to store, manage, and distribute container images within their organization. This guide covers setting up a secure, production-ready Docker registry with authentication, encryption, and best practices.
Docker Registry is an open-source server-side application that stores and distributes Docker images. While Docker Hub serves as a public registry, private registries offer:
Before setting up your registry, ensure you have:
For development or testing purposes, you can quickly deploy a registry:
docker run -d \
--restart=always \
--name registry \
-v /opt/registry/data:/var/lib/registry \
-p 5000:5000 \
registry:2
Warning: This setup is insecure and should only be used for testing. Production deployments require HTTPS and authentication.
Create a directory structure for your registry:
sudo mkdir -p /opt/registry/{data,config,auth,certs}
For production use, you need SSL certificates. Generate self-signed certificates or use certificates from a CA:
# Generate private key
openssl genrsa -out /opt/registry/certs/domain.key 2048
# Generate certificate signing request
openssl req -new -key /opt/registry/certs/domain.key -out /opt/registry/certs/domain.csr
# Generate self-signed certificate
openssl x509 -req -days 365 -in /opt/registry/certs/domain.csr -signkey /opt/registry/certs/domain.key -out /opt/registry/certs/domain.crt
Install htpasswd utility and create user accounts:
# On Ubuntu/Debian
sudo apt-get install apache2-utils
# On CentOS/RHEL/Fedora
sudo yum install httpd-tools
# Create password file with user credentials
htpasswd -Bbn admin password > /opt/registry/auth/htpasswd
Create a configuration file at /opt/registry/config/config.yml:
version: 0.1
log:
fields:
service: registry
storage:
cache:
blobdescriptor: inmemory
filesystem:
rootdirectory: /var/lib/registry
maxthreads: 100
http:
addr: :443
headers:
X-Content-Type-Options: [nosniff]
tls:
certificate: /certs/domain.crt
key: /certs/domain.key
auth:
htpasswd:
realm: basic-realm
path: /auth/htpasswd
health:
storagedriver:
enabled: true
interval: 10s
threshold: 3
Create a docker-compose.yml file:
version: '3.8'
services:
registry:
image: registry:2
restart: always
ports:
- "443:443"
volumes:
- /opt/registry/data:/var/lib/registry
- /opt/registry/config/config.yml:/etc/docker/registry/config.yml
- /opt/registry/certs:/certs
- /opt/registry/auth:/auth
environment:
REGISTRY_HTTP_TLS_CERTIFICATE: /certs/domain.crt
REGISTRY_HTTP_TLS_KEY: /certs/domain.key
REGISTRY_AUTH: htpasswd
REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd
REGISTRY_AUTH_HTPASSWD_REALM: basic-realm
Deploy the registry:
cd /opt/registry
docker-compose up -d
The registry supports multiple storage backends beyond the default filesystem:
storage:
s3:
region: us-east-1
bucket: my-registry-bucket
accesskey: AWS_ACCESS_KEY
secretkey: AWS_SECRET_KEY
encrypt: true
secure: true
v4auth: true
chunksize: 5242880
rootdirectory: /registry
storage:
gcs:
bucket: my-registry-bucket
keyfile: /secrets/gcs-key.json
rootdirectory: /registry
storage:
azure:
accountname: myaccountname
accountkey: myaccountkey
container: mycontainer
rootdirectory: /registry
Configure webhook notifications for events:
notifications:
endpoints:
- name: webhook
url: https://mycompany.example.com/hook
timeout: 5s
threshold: 5
backoff: 1s
For production environments, use certificates from a trusted Certificate Authority (CA):
Configure firewall rules to restrict access:
# Allow only specific IPs to access the registry
sudo ufw allow from 192.168.1.0/24 to any port 443
Integrate with vulnerability scanners like Clair or Trivy:
middleware:
registry:
- name: redis
options:
redis:
addr: localhost:6379
dialtimeout: 10ms
readtimeout: 10ms
writetimeout: 10ms
pool:
maxidle: 16
maxactive: 64
idletimeout: 300s
prefix: redis
After setting up your registry, configure Docker clients to trust it:
Add the certificate to Docker’s trust store:
# Copy the certificate to the Docker client
sudo mkdir -p /etc/docker/certs.d/myregistry.domain.com
sudo cp domain.crt /etc/docker/certs.d/myregistry.domain.com/ca.crt
sudo systemctl restart docker
If using self-signed certificates for testing, add the registry as insecure:
Edit /etc/docker/daemon.json:
{
"insecure-registries": [
"myregistry.domain.com:5000"
]
}
Then restart Docker:
sudo systemctl restart docker
Log in to your private registry:
docker login myregistry.domain.com
# Tag an existing image
docker tag myimage:latest myregistry.domain.com/myimage:latest
# Push to private registry
docker push myregistry.domain.com/myimage:latest
# Pull from private registry
docker pull myregistry.domain.com/myimage:latest
Monitor registry logs:
docker logs -f registry
For centralized logging, configure log drivers in your compose file:
services:
registry:
# ... other config
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
Regularly clean up unused layers:
# Stop the registry
docker-compose stop
# Run garbage collection
docker run --rm -it -v /opt/registry/data:/var/lib/registry --entrypoint bin/registry registry:2 garbage-collect /etc/docker/registry/config.yml
# Restart the registry
docker-compose up -d
Create a backup script:
#!/bin/bash
REGISTRY_DATA="/opt/registry/data"
BACKUP_DIR="/backups/registry"
DATE=$(date +%Y%m%d_%H%M%S)
mkdir -p $BACKUP_DIR
tar -czf $BACKUP_DIR/registry_backup_$DATE.tar.gz -C $REGISTRY_DATA .
Example Jenkins pipeline for pushing to private registry:
pipeline {
agent any
stages {
stage('Build') {
steps {
script {
docker.build("${env.REGISTRY_URL}/myapp:${env.BUILD_NUMBER}")
}
}
}
stage('Push') {
steps {
script {
docker.withRegistry("https://${env.REGISTRY_URL}", 'registry-credentials') {
docker.image("${env.REGISTRY_URL}/myapp:${env.BUILD_NUMBER}").push()
docker.image("${env.REGISTRY_URL}/myapp:${env.BUILD_NUMBER}").push('latest')
}
}
}
}
}
}
Using the private registry with Kubernetes:
apiVersion: v1
kind: Secret
metadata:
name: regcred
type: kubernetes.io/dockerconfigjson
data:
.dockerconfigjson: <base64-encoded-auth>
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
imagePullSecrets:
- name: regcred
containers:
- name: myapp
image: myregistry.domain.com/myapp:latest
ports:
- containerPort: 80
If encountering certificate errors:
Verify certificate validity:
openssl x509 -in /opt/registry/certs/domain.crt -text -noout
Check certificate hostname matches registry URL
Check authentication configuration:
# Verify htpasswd file
cat /opt/registry/auth/htpasswd
Monitor disk space:
df -h /opt/registry/data