A private Docker registry allows organisations to store and distribute container images internally, without relying on Docker Hub or a cloud registry. This is essential for teams working with proprietary application images that cannot be stored in public registries, organisations with strict data sovereignty requirements, and environments with limited internet access. Docker provides an official open-source registry image (registry:2) based on the Docker Distribution project, which supports the OCI Distribution Specification and is compatible with all Docker and container tools. This guide covers deploying a private Docker registry on RHEL 9 with TLS encryption and basic authentication, configuring Docker clients to use it, and managing images in the private registry.

Prerequisites

  • Docker Engine and Docker Compose installed on RHEL 9
  • A domain name or hostname for the registry server

Step 1 — Generate TLS Certificates

mkdir -p /opt/registry/{certs,auth,data}

# Generate self-signed certificate (use Let's Encrypt for production)
openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes 
    -keyout /opt/registry/certs/registry.key 
    -out /opt/registry/certs/registry.crt 
    -subj "/CN=registry.example.com" 
    -addext "subjectAltName=DNS:registry.example.com,IP:192.168.1.100"

Step 2 — Create Basic Authentication

# Install htpasswd tool
dnf install -y httpd-tools

# Create credentials file (htpasswd format)
htpasswd -Bbn registryuser 'RegistryPass123!' > /opt/registry/auth/htpasswd

Step 3 — Deploy the Registry with Docker Compose

# /opt/registry/compose.yaml
services:
  registry:
    image: registry:2
    container_name: registry
    restart: always
    ports:
      - "5000:5000"
    environment:
      REGISTRY_HTTP_ADDR: 0.0.0.0:5000
      REGISTRY_HTTP_TLS_CERTIFICATE: /certs/registry.crt
      REGISTRY_HTTP_TLS_KEY: /certs/registry.key
      REGISTRY_AUTH: htpasswd
      REGISTRY_AUTH_HTPASSWD_REALM: Registry Realm
      REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd
      REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /data
    volumes:
      - /opt/registry/certs:/certs:ro
      - /opt/registry/auth:/auth:ro
      - /opt/registry/data:/data
cd /opt/registry && docker compose up -d
docker compose ps

Step 4 — Configure Docker Clients to Trust the Registry

# On each Docker client, copy the registry's CA certificate
mkdir -p /etc/docker/certs.d/registry.example.com:5000
cp /opt/registry/certs/registry.crt /etc/docker/certs.d/registry.example.com:5000/ca.crt

# No need to restart Docker daemon after adding certs

Step 5 — Push and Pull Images

# Log in to the private registry
docker login registry.example.com:5000
# Username: registryuser
# Password: RegistryPass123!

# Tag and push an image
docker tag nginx:alpine registry.example.com:5000/nginx:alpine
docker push registry.example.com:5000/nginx:alpine

# Pull from the private registry
docker pull registry.example.com:5000/nginx:alpine

# List images in the registry (REST API)
curl -u registryuser:RegistryPass123! 
    https://registry.example.com:5000/v2/_catalog

Conclusion

A private Docker registry on RHEL 9 provides full control over image distribution within your organisation. The TLS certificate and basic authentication are the minimum security requirements — never run a registry without TLS, as Docker image layers can contain sensitive application code, configuration, and credentials. For larger organisations, consider Harbor (enterprise registry with RBAC, vulnerability scanning, and image signing) as an alternative to the basic registry:2 image.

Next steps: How to Install Docker Engine on RHEL 9, How to Build Multi-Stage Docker Images on RHEL 9, and How to Install Portainer on RHEL 9.