Docker Hub is convenient, but you can't always push proprietary code to a public registry. Private registries store images securely within your infrastructure. In this lesson, you'll learn to run your own Docker Registry, implement cleanup policies, and manage images at scale.
1. Learning Objectives
By the end of this lesson, you will be able to:
- Run a private Docker Registry
- Push and pull images from private registry
- Set up authentication for registry access
- Implement image retention policies
- Use Harbor for enterprise registry features
- Integrate registry with CI/CD pipelines
2. Why This Matters
Real-world scenario: Your company has proprietary applications. You can't push them to public Docker Hub. A private registry lets you store images securely on your own infrastructure, with access controls and audit logs.
3. Core Concepts
Docker Registry (Basic)
# Run private registry
docker run -d -p 5000:5000 --name registry registry:2
# Tag image for private registry
docker tag myapp:latest localhost:5000/myapp:latest
# Push to private registry
docker push localhost:5000/myapp:latest
# Pull from private registry
docker pull localhost:5000/myapp:latest
# List images in registry
curl http://localhost:5000/v2/_catalog
# List tags for an image
curl http://localhost:5000/v2/myapp/tags/list
# Delete from registry
curl -X DELETE http://localhost:5000/v2/myapp/manifests/<digest>
# Garbage collect (after deletion)
docker exec registry bin/registry garbage-collect /etc/docker/registry/config.yml
Basic private registry operations
Secure Registry with Authentication
# Generate htpasswd
mkdir auth
docker run --entrypoint htpasswd httpd:2 -Bbn myuser mypassword > auth/htpasswd
# Run registry with authentication
docker run -d \
-p 5000:5000 \
--name registry \
-v $(pwd)/auth:/auth \
-e REGISTRY_AUTH=htpasswd \
-e REGISTRY_AUTH_HTPASSWD_REALM=Registry \
-e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
-v $(pwd)/certs:/certs \
-e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt \
-e REGISTRY_HTTP_TLS_KEY=/certs/domain.key \
registry:2
# Login to registry
docker login localhost:5000
# Docker Compose registry with auth
cat > docker-compose.registry.yml << 'EOF'
version: '3.8'
services:
registry:
image: registry:2
ports:
- "5000:5000"
volumes:
- ./registry-data:/var/lib/registry
- ./auth:/auth
- ./certs:/certs
environment:
REGISTRY_AUTH: htpasswd
REGISTRY_AUTH_HTPASSWD_REALM: Registry
REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd
REGISTRY_HTTP_TLS_CERTIFICATE: /certs/domain.crt
REGISTRY_HTTP_TLS_KEY: /certs/domain.key
restart: unless-stopped
EOF
Secure registry with authentication and TLS
Harbor: Enterprise Registry
# Install Harbor (production-ready registry)
# Download installer
wget https://github.com/goharbor/harbor/releases/download/v2.10.0/harbor-online-installer-v2.10.0.tgz
tar xvf harbor-online-installer-v2.10.0.tgz
cd harbor
# Configure harbor.yml
# hostname: registry.example.com
# harbor_admin_password: Harbor12345
# Install
./install.sh --with-notary --with-trivy --with-chartmuseum
# Harbor features:
# - Role-based access control
# - Vulnerability scanning (Trivy)
# - Image replication across registries
# - Webhooks for CI/CD integration
# - Helm chart repository
# - Retention policies
# Push to Harbor
docker tag myapp:latest registry.example.com/project/myapp:latest
docker login registry.example.com
docker push registry.example.com/project/myapp:latest
# Harbor CLI (harbor-cli)
# Install: pip install harbor-cli
harbor login https://registry.example.com -u admin -p Harbor12345
harbor project list
harbor project create myproject
harbor artifact list myproject/myapp
Harbor enterprise registry setup
Tagging Strategies
# Tagging best practices
# Git commit SHA (uniquely identifiable)
docker tag myapp registry/myapp:git-sha-abc123def
# Semantic version (human readable)
docker tag myapp registry/myapp:1.2.3
docker tag myapp registry/myapp:v1.2.3
# Environment (deployment target)
docker tag myapp registry/myapp:staging
docker tag myapp registry/myapp:production
# Date-based (for audit)
docker tag myapp registry/myapp:2025-02-07
# Multiple tags per image
docker tag myapp registry/myapp:1.2.3
docker tag myapp registry/myapp:v1.2.3
docker tag myapp registry/myapp:latest
docker tag myapp registry/myapp:git-sha-abc123
docker push registry/myapp --all-tags
# CI/CD tagging
export GIT_SHA=$(git rev-parse --short HEAD)
export BUILD_DATE=$(date -u +%Y%m%d)
docker build -t myapp:$GIT_SHA -t myapp:$BUILD_DATE -t myapp:latest .
Image tagging strategies
Registry Cleanup and Retention
# Manual cleanup
docker system prune -a --volumes
# Registry garbage collection
docker exec registry bin/registry garbage-collect /etc/docker/registry/config.yml
# Delete old images from registry (using script)
#!/bin/bash
# delete-old-tags.sh
REGISTRY_URL="localhost:5000"
REPO="myapp"
DAYS=30
cutoff_date=$(date -d "-$DAYS days" +%s)
for tag in $(curl -s https://$REGISTRY_URL/v2/$REPO/tags/list | jq -r '.tags[]'); do
created_date=$(curl -s https://$REGISTRY_URL/v2/$REPO/manifests/$tag | jq -r '.history[0].v1Compatibility' | jq -r '.created')
created_epoch=$(date -d "$created_date" +%s)
if [ $created_epoch -lt $cutoff_date ]; then
echo "Deleting $tag (created $created_date)"
curl -X DELETE https://$REGISTRY_URL/v2/$REPO/manifests/$tag
fi
done
# Harbor retention policies (UI configured)
# Rules:
# - Keep 10 most recent tags
# - Keep tags created in last 30 days
# - Keep tags matching regex 'v\d+\.\d+'
Registry cleanup and retention
4. Complete Project: Enterprise Registry Setup
# docker-compose.full-registry.yml
version: '3.8'
services:
registry:
image: registry:2
ports:
- "5000:5000"
volumes:
- registry_data:/var/lib/registry
- ./auth:/auth
- ./certs:/certs
environment:
REGISTRY_AUTH: htpasswd
REGISTRY_AUTH_HTPASSWD_REALM: Registry
REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd
REGISTRY_HTTP_TLS_CERTIFICATE: /certs/domain.crt
REGISTRY_HTTP_TLS_KEY: /certs/domain.key
REGISTRY_STORAGE_DELETE_ENABLED: "true"
REGISTRY_STORAGE_CACHE_BLOBDESCRIPTOR: redis
REGISTRY_REDIS_ADDR: redis:6379
depends_on:
- redis
restart: unless-stopped
redis:
image: redis:alpine
restart: unless-stopped
registry-ui:
image: joxit/docker-registry-ui:latest
ports:
- "8080:80"
environment:
- REGISTRY_URL=https://registry:5000
- REGISTRY_SECURE=true
- DELETE_IMAGES_ENABLED=true
depends_on:
- registry
restart: unless-stopped
harbor-core:
image: goharbor/harbor-core:latest
depends_on:
- registry
environment:
- DATABASE_TYPE=postgresql
- POSTGRESQL_HOST=postgres
- POSTGRESQL_PORT=5432
- POSTGRESQL_DATABASE=registry
- POSTGRESQL_USERNAME=harbor
- POSTGRESQL_PASSWORD=harbor
- HARBOR_ADMIN_PASSWORD=Harbor12345
volumes:
- harbor_data:/data
postgres:
image: postgres:15-alpine
environment:
POSTGRES_USER: harbor
POSTGRES_PASSWORD: harbor
POSTGRES_DB: registry
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
registry_data:
harbor_data:
postgres_data:
networks:
default:
name: registry-network
Complete enterprise registry stack
CI/CD Integration
# GitHub Actions workflow pushing to private registry
name: Build and Push to Registry
on:
push:
branches: [main]
tags: ['v*']
env:
REGISTRY: registry.example.com
IMAGE_NAME: myapp
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=sha,prefix={{sha}}
type=raw,value=latest,enable={{is_default_branch}}
type=semver,pattern={{version}}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
scan:
needs: build-and-push
runs-on: ubuntu-latest
steps:
- name: Scan image with Trivy
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
format: 'sarif'
output: 'trivy-results.sarif'
- name: Upload scan results
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: 'trivy-results.sarif'
CI/CD integration with private registry
5. Common Errors & Solutions
6. Summary Checklist
7. Next Steps
Next category: Kubernetes - Container Orchestration (Lessons 6.2-6.7)
Comments (0)
This is exactly what I needed! The initContainer approach solved our migration issues completely. Thanks for the detailed guide!
ReplyLeave a Comment