In this tutorial, we are going to learn how to setup secure Docker private registry on Ubuntu 24.04. We will cover everything from generating SSL certificates with Subject Alternative Names (SANs) to configuring Nginx as a reverse proxy within a Docker Compose setup, and finally, securing your registry with basic authentication. By the end of this tutorial, you’ll have a fully functional and secure private registry ready for your development and deployment workflows
Why Private Docker Registry ?
For Smooth CI/CD development using the docker platform, consider using a self-hosted docker registry server. Docker registry is the repository where you can store your docker images and pull them to run applications on the server. For faster delivery as well as secure infrastructure, it is recommended to set up your own docker private registry to store your docker images and distribute among organizations.
Prerequisites
- Two Linux Servers (Ubuntu 24.04): You will need one server to host your Docker Registry (the registry host) and another to act as a client for pushing and pulling images. These can be virtual machines or cloud instances.
- User account with sudo privileges
- Private Registry FQDN: registry.linuxtechi.org
- Pre install Docker and Docker-Compose on both servers.
What is Docker Registry?
Docker Registry is a Server-side application which allows you to store your docker images locally into one centralized location. By setting up your own docker registry server, you can pull and push docker images without having to connect to the Docker hub, saving your bandwidth and preventing you from security threats.
Before You start
Before starting, I ensure that you have installed Docker and Docker-Compose on both client server and local registry server. To verify you have installed required software, you can run the following commands to check the software version.
docker version docker-compose version
Also, you need to ensure that docker service is started and is setup to enable at boot time:
sudo systemctl start docker sudo systemctl enable docker
Steps to Setup Secure Docker Private Registry
This section outlines the detailed steps to set up your secure Docker private registry. We will primarily work on our registry host server.
1) Generate SSL Certificates with Subject Alternative Names (SANs)
Modern browsers and Docker clients require SSL certificates to include Subject Alternative Names (SANs) rather than relying solely on the Common Name (CN) field. This is crucial to avoid errors like ‘x509: certificate relies on legacy Common Name field, use SANs instead‘. We will generate self-signed certificates for this tutorial. For production environments, it is highly recommended to use certificates from a trusted Certificate Authority (CA) like Let’s Encrypt.
First, create a directory to store your certificates and navigate into it:
mkdir -p ~/private-registry/certs cd ~/private-registry/certs
Next, create an OpenSSL configuration file named ‘openssl.cnf‘. This file will define the certificate details, including the SANs. Replace ‘registry.linuxtechi.org‘ with your actual FQDN.
vi openssl.cnf
[req] distinguished_name = req_distinguished_name req_extensions = v3_req prompt = no [req_distinguished_name] C = IN ST = New Delhi L = New Delhi O = LinuxTechi OU = IT Department CN = registry.linuxtechi.org [v3_req] keyUsage = nonRepudiation, digitalSignature, keyEncipherment extendedKeyUsage = serverAuth subjectAltName = @alt_names [alt_names] DNS.1 = registry.linuxtechi.org
openssl genrsa -out registry.linuxtechi.org.key 2048 openssl req -new -key registry.linuxtechi.org.key -out registry.linuxtechi.org.csr -config openssl.cnf
openssl x509 -req -days 365 -in registry.linuxtechi.org.csr -signkey registry.linuxtechi.org.key -out registry.linuxtechi.org.crt -extensions v3_req -extfile openssl.cnf

2) Prepare Your Docker Compose File with Registry and Nginx
You need to create a new docker-compose.yml script that defines the docker-compose version and services required to set up a private registry.
Instead of running Nginx as a system daemon, we will containerize it within our Docker Compose setup. This approach offers better portability and easier management. First, create the necessary directory structure for Nginx configuration:
mkdir -p ~/private-registry/nginx/conf.d
Now, navigate to your ~/private-registry directory and create a docker-compose.yml file. This file will define both your Docker Registry service and your Nginx reverse proxy service.
cd ~/private-registry
vi docker-compose.yml
Add the following content to docker-compose.yml file:
services: registry: image: registry:latest ports: - "5000:5000" environment: REGISTRY_AUTH: htpasswd REGISTRY_AUTH_HTPASSWD_REALM: Registry REGISTRY_AUTH_HTPASSWD_PATH: /auth/registry.password REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /registry-data volumes: - ./auth:/auth - ./registry-data:/registry-data restart: always nginx: image: nginx:latest ports: - "80:80" - "443:443" volumes: - ./nginx/conf.d:/etc/nginx/conf.d - ./certs:/etc/nginx/ssl depends_on: - registry restart: always
Save and close the file
Let’s break down this docker-compose.yml configuration:
registry service:
- image: registry:latest: Pulls the official Docker Registry image.
- ports:”5000:5000″: Maps the container’s port 5000 to the host’s port 5000. While Nginx will handle external access, this mapping can be useful for debugging.
- environment: Configures the registry for basic authentication using htpasswd.
- volumes: Persists authentication data and registry images on the host machine, ensuring data is not lost when the container is restarted.
nginx service:
- image: nginx:latest: Pulls the official Nginx image.
- ports: “80:80”, “443:443”: Exposes Nginx on the standard HTTP and HTTPS ports.
- volumes: Mounts our custom Nginx configuration and the generated SSL certificates into the Nginx container.
- depends_on: – registry: Ensures that the registry service is started before the Nginx service.
3) Configure Nginx Reverse Proxy
Now, we need to create the Nginx configuration file that will be mounted into our Nginx container. This file will instruct Nginx on how to proxy requests to our Docker registry and how to use the SSL certificates we generated.
Create a new file named default.conf in the ~/private-registry/nginx/conf.d
vi ~/private-registry/nginx/conf.d/default.conf
Add the following Nginx configuration to the default.conf file. Remember to replace registry.linuxtechi.org with your actual FQDN.
server { listen 80; server_name registry.linuxtechi.org; return 301 https://$host$request_uri; } server { listen 443 ssl; server_name registry.linuxtechi.org; ssl_certificate /etc/nginx/ssl/registry.linuxtechi.org.crt; ssl_certificate_key /etc/nginx/ssl/registry.linuxtechi.org.key; # Disable SSLv3 to prevent POODLE attack ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers on; ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH'; client_max_body_size 0; chunked_transfer_encoding on; location / { proxy_pass http://registry:5000; 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; # Docker Registry specific headers proxy_set_header Authorization $http_authorization; proxy_pass_request_headers on; proxy_buffering off; } }
save and exit the file. Now, start the containers using following commands.
cd ~/private-registry && docker compose up -d
This command will build and start both your registry and Nginx containers in detached mode. You should see output indicating that the services have been created and started successfully.
Verify the container status, run
docker ps
4) Set Up Basic Authentication with htpasswd
sudo apt install apache2-utils -y
Now, create your first user. Replace devops with your desired username. You will be prompted to enter and confirm a password.
sudo htpasswd -Bc ~/private-registry/auth/registry.password devops
The -B flag forces bcrypt encryption for the password, and the -c flag creates the password file. For subsequent users, omit the -c flag to append to the existing file:
5) Configure Docker Client to Trust Your Registry
First, copy your self-signed SSL certificate (registry.linuxtechi.org.crt) from your registry host to your client machine. You can use scp or any other secure file transfer method. Then, create a directory for your registry’s certificate and copy the certificate into it:
sudo mkdir -p /etc/docker/certs.d/registry.linuxtechi.org:443 sudo cp registry.linuxtechi.org.crt /etc/docker/certs.d/registry.linuxtechi.org:443/ca.crt
Now, restart the Docker daemon for the changes to take effect:
sudo systemctl daemon-reload && sudo systemctl restart docker
6) Test Your Private Registry
Now it’s time to test your secure private registry. On your client machine, log in to your registry using the username and password you created in Step 4:
docker login registry.linuxtechi.org:443
docker tag hello-world registry.linuxtechi.org:443/devops/my-app:latest docker push registry.linuxtechi.org:443/devops/my-app:latest

Once push is completed, you can go to browser and enter the url:
https://registry.linuxtechi.org/v2/_catalog
Replace registry.linuxtechi.org with your own url and provide basic authentication. You will find repositories list as :
docker rmi registry.linuxtechi.org:443/devops/my-app:latest docker pull registry.linuxtechi.org:443/devops/my-app:latest
If the pull is successful, congratulations! You have successfully set up a secure Docker private registry.
That’s all from this tutorial, I hope you have found it useful and informative. Kindly do post your queries and feedback in below comments section.
Also Read : How to Install KVM on Ubuntu 24.04 Step-by-Step
Great guide but I found a small typo, the compose file has
REGISTRY_AUTH_HTPASSWD_PATH: /auth/registry.passwd
but then you create the htpasswd file as registry.password
needs to be
htpasswd -Bc registry.passwd linuxtechi
docker log:
msg=”error checking authorization: stat /auth/registry.passwd: no such file or directory”
ERROR: The Compose file ‘./docker-compose.yml’ is invalid because:
Unsupported config option for networks: ‘mynet’
Unsupported config option for volumes: ‘myregistrydata’
Unsupported config option for services: ‘registry’
Not working 🙁