Docker Registry â Private Registry Setup & Management Guide
In this tutorial, you'll learn about Docker Registry. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
Docker Registry is a server-side application for storing and distributing Docker container images, enabling private image storage with access control, Caching, and integration with CI/CD pipelines.
What You'll Learn
Why It Matters
Pushing proprietary container images to public registries (Docker Hub) exposes your intellectual property and introduces dependency on external infrastructure. A private Docker Registry keeps images within your network, reduces pull latency, and gives you full control over access, retention, and storage costs. DodaTech's private registry stores 5,000+ container images across development, staging, and production environments.
Real-World Use
DodaZIP's CI pipeline builds a container image for each microservice, tags it with the Git commit SHA, pushes it to the private Docker Registry, and triggers a rolling Kubernetes deployment. The registry is fronted by a CDN-backed proxy for global pull performance.
flowchart TD
A[CI Pipeline] --> B[Docker Build]
B --> C[Docker Registry]
C --> D[Storage Backend]
D --> E[S3 / GCS / Azure]
C --> F[Authentication]
F --> G[htpasswd / OAuth2]
C --> H[TLS Termination]
C --> I[Kubernetes Pull]
I --> J[Node 1]
I --> K[Node 2]
I --> L[Node N]
style C fill:#2496ED,color:#fff
Prerequisites: Docker installed. Familiarity with TLS certificates and storage backends.
Installation
# Run the official Docker Registry
docker run -d \
--name docker-registry \
-p 5000:5000 \
--restart always \
-v registry_data:/var/lib/registry \
registry:2.8.3
# Expected output:
# Container ID returned
# Verify
curl http://localhost:5000/v2/
# Expected output:
# {}
# Check API version
curl http://localhost:5000/v2/_catalog
# Expected output:
# {"repositories":[]}
Configuration with Docker Compose
# docker-compose.yml
version: '3.8'
services:
registry:
image: registry:2.8.3
ports:
- "5000:5000"
- "5001:5001"
environment:
REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /data
REGISTRY_HTTP_SECRET: changethissecret
REGISTRY_STORAGE_DELETE_ENABLED: "true"
REGISTRY_COMPATIBILITY_SCHEMA1_ENABLED: "false"
volumes:
- registry_data:/data
- ./certs:/certs
- ./auth:/auth
- ./config.yml:/etc/docker/registry/config.yml
restart: always
volumes:
registry_data:
# config.yml
version: 0.1
log:
level: info
fields:
service: registry
storage:
cache:
blobdescriptor: inmemory
filesystem:
rootdirectory: /data
delete:
enabled: true
http:
addr: :5000
secret: changethissecret
headers:
X-Content-Type-Options: [nosniff]
Access-Control-Allow-Origin: ['*']
Access-Control-Allow-Methods: ['HEAD', 'GET', 'OPTIONS', 'DELETE']
tls:
certificate: /certs/registry.crt
key: /certs/registry.key
auth:
htpasswd:
realm: basic-realm
path: /auth/htpasswd
health:
storagedriver:
enabled: true
interval: 10s
threshold: 3
Authentication Setup
# Create htpasswd file with bcrypt
docker run --entrypoint htpasswd \
httpd:2.4 -Bbn admin SecureP@ss1 > auth/htpasswd
# Add another user
docker run --entrypoint htpasswd \
httpd:2.4 -Bbn deploy-team DeployP@ss2 >> auth/htpasswd
# Verify the file
cat auth/htpasswd
# Expected output:
# admin:$2y$05$abc...
# deploy-team:$2y$05$def...
# Restart registry to pick up auth
docker compose restart registry
# Test authentication
curl -u admin:SecureP@ss1 http://localhost:5000/v2/_catalog
# Expected output:
# {"repositories":[]}
# Without auth (should fail)
curl http://localhost:5000/v2/_catalog
# Expected output:
# {"errors":[{"code":"UNAUTHORIZED","message":"authentication required"}]}
Storage Backend Configuration
# config.yml â S3 storage backend
storage:
s3:
accesskey: AKIAIOSFODNN7EXAMPLE
secretkey: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
region: us-east-1
bucket: dodatech-registry
rootdirectory: /docker/registry
encrypt: true
keyid: arn:aws:kms:us-east-1:123456789012:key/abc123
secure: true
skipverify: false
v4auth: true
multipart: true
chunksize: "5242880" # 5MB chunks for uploads
# config.yml â GCS storage backend
storage:
gcs:
bucket: dodatech-registry
keyfile: /gcs-key.json
rootdirectory: /docker/registry
chunksize: 5242880
# config.yml â Azure storage backend
storage:
azure:
accountname: dodatechregistry
accountkey: your-azure-storage-key
container: registry
realm: core.windows.net
Push and Pull Images
# Login to private registry
docker login registry.dodatech.com:5000 -u admin -p SecureP@ss1
# Expected output:
# Login Succeeded
# Tag and push an image
docker tag dodazip:latest registry.dodatech.com:5000/dodazip:1.0.0
docker push registry.dodatech.com:5000/dodazip:1.0.0
# Expected output:
# The push refers to repository [registry.dodatech.com:5000/dodazip]
# 2a3b5c7d8e9f: Pushed
# 3b4c6d8e0f1a: Pushed
# 1.0.0: digest: sha256:abc123... size: 1784
# Pull from private registry
docker pull registry.dodatech.com:5000/dodazip:1.0.0
# List tags
curl -u admin:SecureP@ss1 \
http://localhost:5000/v2/dodazip/tags/list
# Expected output:
# {"name":"dodazip","tags":["1.0.0","latest","1.0.1-rc1"]}
Garbage Collection
# Enable delete in config.yml first (storage.delete.enabled: true)
# Delete a manifest
curl -u admin:SecureP@ss1 -X DELETE \
http://localhost:5000/v2/dodazip/manifests/sha256:abc123
# Expected output:
# 202 Accepted
# Run garbage collection (inside the container)
docker exec registry-registry-1 \
registry garbage-collect /etc/docker/registry/config.yml
# Expected output:
# blobs marked for deletion: 15
# blobs deleted: 12
# blobs that cannot be deleted: 0
# Dry run first
docker exec registry-registry-1 \
registry garbage-collect --dry-run /etc/docker/registry/config.yml
CI/CD Integration
# .github/workflows/docker-publish.yml
name: Build and Push
on:
push:
branches: [main]
tags: ['v*']
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Log in to private registry
uses: docker/login-action@v3
with:
registry: registry.dodatech.com:5000
username: ${{ secrets.REGISTRY_USER }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
registry.dodatech.com:5000/dodazip:latest
registry.dodatech.com:5000/dodazip:${{ github.sha }}
registry.dodatech.com:5000/dodazip:${{ github.ref_name }}
# Kubernetes pull secret
apiVersion: v1
kind: Secret
metadata:
name: registry-credentials
namespace: production
type: kubernetes.io/dockerconfigjson
data:
.dockerconfigjson: eyJhdXRocyI6eyJyZWdpc3RyeS5kb2RhdGVjaC5jb206NTAwMCI6eyJ1c2VybmFtZSI6ImFkbWluIiwicGFzc3dvcmQiOiJTZWN1cmVQQHNzMSIsImF1dGgiOiJZV1J0YVc0NmRHNW5kR2x6ZEhOemRITT0ifX19
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: dodazip
namespace: production
spec:
template:
spec:
imagePullSecrets:
- name: registry-credentials
containers:
- name: dodazip
image: registry.dodatech.com:5000/dodazip:1.0.0
Common Configuration Mistakes
Running registry without TLS: Docker requires HTTPS for registries (except
localhost). Without TLS, clients refuse to push/pull. Use a reverse proxy (NGINX, Traefik) or configure TLS directly.Not enabling Garbage Collection: Deleted manifests leave orphaned blob layers on disk. Schedule regular Garbage Collection to reclaim storage space.
Using the filesystem backend on ephemeral storage: Pod restarts delete all images. Use S3, GCS, or Azure Blob storage for durable, scalable storage.
Missing
delete.enabled: truefor cleanup: Without this flag, the API rejects DELETE requests and Garbage Collection cannot remove layers.Weak
http.secretvalue: The secret is used to sign upload tokens. A weak or default secret allows token forgery. Generate a random 64-character string.
Practice Questions
What is the difference between a Docker Registry and a Docker Hub? Answer: Docker Registry is the server software for storing images. Docker Hub is a hosted service running Docker Registry with additional features like automated builds and teams.
Why is TLS required for Docker registries? Answer: Docker clients enforce TLS for non-localhost registries to prevent man-in-the-middle attacks and credential interception.
How does Garbage Collection work in Docker Registry? Answer: GC scans blob storage, identifies unreferenced blob layers (no manifest points to them), and deletes them. It requires
storage.delete.enabled: true.What storage backends does Docker Registry support? Answer: Filesystem (local), S3 (AWS), GCS (Google), Azure Blob, Swift (OpenStack), and Alibaba OSS via configuration.
Challenge
Deploy a production-grade Docker Registry: configure TLS with Let's Encrypt certificates, set up htpasswd authentication with bcrypt, use S3 as the storage backend, enable delete and configure Garbage Collection as a cron job, integrate with a CI/CD pipeline that pushes on every Git tag, create Kubernetes pull secrets, and set up monitoring with Prometheus metrics endpoint.
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro