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
dockerservice running - Root or
sudoaccess - 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.