Skip to main content
Back to Category

Private Docker Registry Setup: Image Storage and Management

Private Docker Registry setup, secure registry with TLS, authentication, image push/pull operations, enterprise registry with Harbor, and CI/CD integration.

Read time: 14 dk Container & Orchestration
dockerregistryimageharborcontainerci/cdstorageprivate registry

Table of Contents

Private Docker Registry Setup

While public registries like Docker Hub are convenient, hosting your own registry for private images offers advantages in security, speed, and cost. This guide covers options from simple Docker Registry to enterprise Harbor.

Why a Private Registry?

AdvantageDescription
SecurityPrivate images not exposed publicly
SpeedFast pull/push on local network
CostNo Docker Hub rate limits
ControlFull image lifecycle control
ComplianceData localization requirements

1. Simple Docker Registry (registry:2)

Basic Setup

hljs bash
# Start registry container
docker run -d \
  --name registry \
  --restart always \
  -p 5000:5000 \
  -v registry-data:/var/lib/registry \
  registry:2

# Verify registry is running
curl http://localhost:5000/v2/
# Output: {}

Image Push and Pull

hljs bash
# Tag image
docker tag myapp:latest localhost:5000/myapp:latest

# Push to registry
docker push localhost:5000/myapp:latest

# Pull from registry
docker pull localhost:5000/myapp:latest

# List images in registry
curl http://localhost:5000/v2/_catalog
# Output: {"repositories":["myapp"]}

# List image tags
curl http://localhost:5000/v2/myapp/tags/list

2. Secure Registry with TLS

Creating a Self-Signed Certificate

hljs bash
# Create certificate directory
mkdir -p /etc/docker/certs/registry

# Create self-signed certificate
openssl req -newkey rsa:4096 -nodes -sha256 \
  -keyout /etc/docker/certs/registry/domain.key \
  -x509 -days 365 \
  -out /etc/docker/certs/registry/domain.crt \
  -subj "/CN=registry.example.com" \
  -addext "subjectAltName=DNS:registry.example.com,IP:192.168.1.100"

# Add certificate to Docker's trusted certificates
mkdir -p /etc/docker/certs.d/registry.example.com:5000
cp /etc/docker/certs/registry/domain.crt \
   /etc/docker/certs.d/registry.example.com:5000/ca.crt

# Restart Docker
systemctl restart docker

Starting Registry with TLS

hljs bash
docker run -d \
  --name registry \
  --restart always \
  -p 5000:5000 \
  -v registry-data:/var/lib/registry \
  -v /etc/docker/certs/registry:/certs \
  -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt \
  -e REGISTRY_HTTP_TLS_KEY=/certs/domain.key \
  registry:2

3. Registry with Authentication

hljs bash
# Create htpasswd file
mkdir -p /etc/docker/registry/auth
docker run --rm \
  --entrypoint htpasswd \
  httpd:2 -Bbn admin StrongPass123! > /etc/docker/registry/auth/htpasswd

# Start registry with authentication
docker run -d \
  --name registry \
  --restart always \
  -p 5000:5000 \
  -v registry-data:/var/lib/registry \
  -v /etc/docker/certs/registry:/certs \
  -v /etc/docker/registry/auth:/auth \
  -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt \
  -e REGISTRY_HTTP_TLS_KEY=/certs/domain.key \
  -e REGISTRY_AUTH=htpasswd \
  -e REGISTRY_AUTH_HTPASSWD_REALM="Registry Realm" \
  -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
  registry:2

# Login
docker login registry.example.com:5000

4. Registry with Docker Compose

hljs yaml
# registry-compose.yml
version: '3.8'

services:
  registry:
    image: registry:2
    restart: always
    ports:
      - "5000:5000"
    environment:
      REGISTRY_HTTP_TLS_CERTIFICATE: /certs/domain.crt
      REGISTRY_HTTP_TLS_KEY: /certs/domain.key
      REGISTRY_AUTH: htpasswd
      REGISTRY_AUTH_HTPASSWD_REALM: Registry Realm
      REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd
      REGISTRY_STORAGE_DELETE_ENABLED: "true"
    volumes:
      - registry-data:/var/lib/registry
      - ./certs:/certs:ro
      - ./auth:/auth:ro

  registry-ui:
    image: joxit/docker-registry-ui:latest
    restart: always
    ports:
      - "8080:80"
    environment:
      - REGISTRY_TITLE=REXE Private Registry
      - REGISTRY_URL=https://registry.example.com:5000
      - SINGLE_REGISTRY=true
    depends_on:
      - registry

volumes:
  registry-data:

5. Harbor — Enterprise Registry

Harbor is a CNCF-supported enterprise-grade container registry.

hljs bash
# Download Harbor installer
wget https://github.com/goharbor/harbor/releases/download/v2.9.0/harbor-online-installer-v2.9.0.tgz
tar xzf harbor-online-installer-v2.9.0.tgz
cd harbor

# Edit configuration
cp harbor.yml.tmpl harbor.yml
nano harbor.yml
hljs yaml
# harbor.yml (basic settings)
hostname: registry.example.com

https:
  port: 443
  certificate: /etc/harbor/certs/domain.crt
  private_key: /etc/harbor/certs/domain.key

harbor_admin_password: StrongPass123!

data_volume: /data/harbor
hljs bash
# Install Harbor
./install.sh

# Login to Harbor
docker login registry.example.com
# User: admin
# Password: StrongPass123!

# Create project and push image
docker tag myapp:latest registry.example.com/myproject/myapp:latest
docker push registry.example.com/myproject/myapp:latest

6. Registry Cleanup (Garbage Collection)

hljs bash
# Clean up unused layers
docker exec registry registry garbage-collect \
  /etc/docker/registry/config.yml

# Dry-run (check before deleting)
docker exec registry registry garbage-collect \
  --dry-run \
  /etc/docker/registry/config.yml

# Delete image (via API)
curl -X DELETE \
  https://registry.example.com:5000/v2/myapp/manifests/DIGEST \
  --cacert /etc/docker/certs/registry/domain.crt \
  -u admin:StrongPass123!

7. CI/CD Integration

With GitHub Actions

hljs yaml
# .github/workflows/build-push.yml
name: Build and Push

on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Login to Private Registry
        uses: docker/login-action@v3
        with:
          registry: registry.example.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.example.com:5000/myapp:${{ github.sha }}

Using Private Registry in Kubernetes

hljs bash
# Add registry credentials as secret
kubectl create secret docker-registry registry-secret \
  --docker-server=registry.example.com:5000 \
  --docker-username=admin \
  --docker-password=StrongPass123! \
  --docker-email=admin@example.com
hljs yaml
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  template:
    spec:
      imagePullSecrets:
        - name: registry-secret
      containers:
        - name: myapp
          image: registry.example.com:5000/myapp:latest

Conclusion

A private Docker Registry is essential for image security and management in enterprise environments. Choose registry:2 for simple use cases, Harbor for enterprise features (RBAC, security scanning, replication). With a private registry on REXE servers, you can securely store your images and integrate them into your CI/CD pipelines.