A private Docker registry lets you store and distribute container images within your organization without relying on Docker Hub or other public registries. Running your own registry gives you full control over image storage, access policies, and network traffic. On RHEL 8, Docker CE is available from the official Docker repository and pairs cleanly with a self-hosted registry. This tutorial walks through deploying a private Docker registry with TLS encryption, optional HTTP basic authentication, and local push/pull workflows.

Prerequisites

  • RHEL 8 server with Docker CE installed and the docker service running
  • Root or sudo access
  • A resolvable hostname or IP address for your registry host
  • OpenSSL installed (dnf install -y openssl)
  • Firewalld active or ports reachable on your network

Step 1 — Start a Basic Registry Container

The official registry:2 image provides a minimal but production-capable OCI distribution server. Mount a host directory so images persist across container restarts.

sudo mkdir -p /registry
sudo docker run -d 
  -p 5000:5000 
  --restart=always 
  --name registry 
  -v /registry:/var/lib/registry 
  registry:2

Confirm it is running:

sudo docker ps --filter name=registry

Step 2 — Generate a Self-Signed TLS Certificate

Docker clients refuse to communicate with a registry over plain HTTP by default. Create a self-signed certificate for development and LAN deployments.

sudo mkdir -p /registry/certs
sudo openssl req -newkey rsa:4096 -nodes -sha256 
  -keyout /registry/certs/domain.key 
  -x509 -days 365 
  -out /registry/certs/domain.crt 
  -subj "/CN=registry.lan"

Stop the plain registry and relaunch it with TLS enabled:

sudo docker rm -f registry
sudo docker run -d 
  -p 5000:5000 
  --restart=always 
  --name registry 
  -v /registry:/var/lib/registry 
  -v /registry/certs:/certs 
  -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt 
  -e REGISTRY_HTTP_TLS_KEY=/certs/domain.key 
  registry:2

Step 3 — Trust the Certificate on Client Machines

Every Docker host that will push or pull from this registry must trust the certificate. On RHEL 8 clients:

# Copy the certificate to the client (run on client)
sudo mkdir -p /etc/docker/certs.d/registry.lan:5000
sudo scp [email protected]:/registry/certs/domain.crt 
  /etc/docker/certs.d/registry.lan:5000/ca.crt

For LAN environments where installing the certificate is impractical, add the registry to Docker’s insecure list instead. Edit /etc/docker/daemon.json on each client:

{
  "insecure-registries": ["registry.lan:5000"]
}

Reload Docker after editing:

sudo systemctl restart docker

Step 4 — Push and Pull Images

Tag a local image with the registry hostname and port, then push it.

sudo docker pull nginx:alpine
sudo docker tag nginx:alpine registry.lan:5000/nginx:alpine
sudo docker push registry.lan:5000/nginx:alpine

Pull the same image from the registry on any trusted client:

sudo docker pull registry.lan:5000/nginx:alpine

List all repositories stored in the registry via its API:

curl -k https://registry.lan:5000/v2/_catalog

Step 5 — Add HTTP Basic Authentication with htpasswd

Restrict access to authenticated users using Apache-style htpasswd files. Install the httpd-tools package to get the htpasswd binary.

sudo dnf install -y httpd-tools
sudo mkdir -p /registry/auth
sudo htpasswd -Bbn registryuser StrongPassword123 
  | sudo tee /registry/auth/htpasswd

Relaunch the registry with both TLS and authentication:

sudo docker rm -f registry
sudo docker run -d 
  -p 5000:5000 
  --restart=always 
  --name registry 
  -v /registry:/var/lib/registry 
  -v /registry/certs:/certs 
  -v /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

Log in from any Docker client before pushing or pulling:

sudo docker login registry.lan:5000

Step 6 — Open the Firewall Port

Allow external clients to reach port 5000 through RHEL 8’s firewalld:

sudo firewall-cmd --permanent --add-port=5000/tcp
sudo firewall-cmd --reload
sudo firewall-cmd --list-ports

Conclusion

You now have a private Docker registry on RHEL 8 with persistent image storage, TLS encryption, and HTTP basic authentication. Images can be pushed from any authorized Docker host and pulled back on demand, keeping sensitive container workloads entirely within your network. For production environments, consider replacing the self-signed certificate with one issued by an internal CA or Let’s Encrypt, and back up the /registry directory regularly.

Next steps: How to Deploy Applications with Docker Compose on RHEL 8, How to Install Portainer for Docker Management on RHEL 8, and How to Install Podman as a Rootless Docker Alternative on RHEL 8.