How to Set Up a Private Docker Registry on RHEL 7
Running a private Docker registry gives your team a secure, internal location to store and distribute container images without relying on Docker Hub or other public registries. Whether you are enforcing air-gapped security policies, reducing external bandwidth, or simply keeping proprietary application images off the internet, a self-hosted registry is an essential part of a mature container workflow. This tutorial walks through deploying the official Docker Registry v2 image on RHEL 7, securing it with TLS and basic authentication using htpasswd, placing it behind an Nginx reverse proxy, and configuring other hosts to use the registry.
Prerequisites
- RHEL 7 server with at least 2 GB RAM and 20 GB free disk space for image storage
- Docker CE installed and the
dockerservice running (systemctl status docker) - Root or sudo access
- A hostname or IP address for the registry server (this guide uses
registry.example.com) - Port 443 (or 5000) open in firewalld
- Optional: a valid TLS certificate and key, or the ability to generate a self-signed one
Step 1: Install Required Packages
Before deploying the registry container you need the httpd-tools package for the htpasswd utility and openssl for certificate generation. Install them with yum:
sudo yum install -y httpd-tools openssl nginx
If Nginx is not available in your base repos, enable the EPEL repository first:
sudo yum install -y epel-release
sudo yum install -y nginx
Step 2: Generate a TLS Certificate
The Docker client refuses to push or pull from a registry that uses plain HTTP unless the registry is explicitly listed as insecure. Using TLS is the recommended approach for any environment beyond a single developer laptop. Create the directory structure and generate a self-signed certificate:
sudo mkdir -p /opt/registry/{certs,auth,data}
sudo openssl req -newkey rsa:4096 -nodes -sha256
-keyout /opt/registry/certs/domain.key
-x509 -days 365
-out /opt/registry/certs/domain.crt
-subj "/CN=registry.example.com"
-addext "subjectAltName=DNS:registry.example.com"
If you have a certificate signed by a trusted CA, copy the .crt and .key files into /opt/registry/certs/ instead.
Step 3: Create Registry Authentication with htpasswd
The registry supports HTTP Basic Authentication backed by a bcrypt-hashed password file. Create credentials for your first registry user:
sudo htpasswd -Bbn registryuser StrongPassword123
| sudo tee /opt/registry/auth/htpasswd
Add additional users by appending to the same file:
sudo htpasswd -Bb /opt/registry/auth/htpasswd devuser AnotherPass456
Step 4: Run the Registry Container
Start the registry using the official registry:2 image. Mount the certificate, auth, and data directories into the container:
sudo docker run -d
--name private-registry
--restart=always
-p 5000:5000
-v /opt/registry/data:/var/lib/registry
-v /opt/registry/certs:/certs
-v /opt/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="Private Registry"
-e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd
registry:2
Verify the container is running:
sudo docker ps | grep private-registry
Step 5: Configure Nginx as a Reverse Proxy
Placing Nginx in front of the registry allows you to serve on standard HTTPS port 443 and add extra headers or rate limiting. Create the Nginx configuration:
sudo tee /etc/nginx/conf.d/registry.conf <<'EOF'
upstream docker-registry {
server 127.0.0.1:5000;
}
server {
listen 443 ssl;
server_name registry.example.com;
ssl_certificate /opt/registry/certs/domain.crt;
ssl_certificate_key /opt/registry/certs/domain.key;
# Increase max body size for large image layers
client_max_body_size 2g;
location / {
proxy_pass https://docker-registry;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 900;
proxy_ssl_verify off;
}
}
server {
listen 80;
server_name registry.example.com;
return 301 https://$host$request_uri;
}
EOF
Test the configuration and enable Nginx:
sudo nginx -t
sudo systemctl enable nginx
sudo systemctl start nginx
Step 6: Open the Firewall
sudo firewall-cmd --permanent --add-port=443/tcp
sudo firewall-cmd --permanent --add-port=5000/tcp
sudo firewall-cmd --reload
Step 7: Push and Pull Images to the Private Registry
On the registry host itself (or any host that trusts the certificate), log in and push an image:
# Log in
sudo docker login registry.example.com
# Tag a local image for your registry
sudo docker tag nginx:latest registry.example.com/myapp/nginx:1.0
# Push the image
sudo docker push registry.example.com/myapp/nginx:1.0
# Pull the image back
sudo docker pull registry.example.com/myapp/nginx:1.0
Step 8: Configure Client Hosts to Trust the Registry
On each client host that needs to use the registry, copy the CA certificate and add it to Docker’s trust store:
# Create the certificate directory for Docker (name must match host:port)
sudo mkdir -p /etc/docker/certs.d/registry.example.com
# Copy the registry certificate (from the registry server or your CA)
sudo scp [email protected]:/opt/registry/certs/domain.crt
/etc/docker/certs.d/registry.example.com/ca.crt
# Restart Docker to pick up the new certificate
sudo systemctl restart docker
If you prefer using an insecure registry (not recommended for production), add the host to /etc/docker/daemon.json on each client:
sudo tee /etc/docker/daemon.json <<'EOF'
{
"insecure-registries": ["registry.example.com:5000"]
}
EOF
sudo systemctl restart docker
Step 9: Verify the Registry API
You can query the registry’s V2 API to list all stored repositories:
curl -u registryuser:StrongPassword123
https://registry.example.com/v2/_catalog
# List tags for a specific image
curl -u registryuser:StrongPassword123
https://registry.example.com/v2/myapp/nginx/tags/list
Conclusion
You now have a fully operational private Docker registry on RHEL 7, secured with TLS and HTTP Basic Authentication, and fronted by an Nginx reverse proxy. Client machines need only the CA certificate placed in Docker’s certificate directory to push and pull images seamlessly. From here you can extend the setup with storage backends such as S3-compatible object storage, configure registry garbage collection to reclaim disk space from deleted layers, or integrate with a CI/CD pipeline to automatically publish build artifacts to your private registry.