How to set up and use a private Docker registry

When working on complex projects with Docker, using Docker Hub is a convenient way to handle images. However, when storing and sharing images internally or for sensitive applications you don’t want to expose publicly, a public registry like Docker Hub might not cut it.
In this guide, we’ll learn how to set up and use Docker private registries for effective and secure Docker image storage and management.
What is a private Docker registry?
A private registry offers a secure and centralized location for Docker images, which gives developers control over who can access and manage them.
This offers a number of benefits:
- Reduced dependency on third-party services for more control in the event of outages, disruptions, or breaches.
- Easy access to images and versioning, so you can push and pull your images like code.
- Faster performance thanks to centralized hosting.
- Better customization, as you get full control over the configuration of your private registry.
- Improved security, as access is restricted to authorized users.
Hosting your registry on a VPS enhances these benefits by adding an extra layer of security, flexibility, and cost efficiency. It offers easy scaling, full control and customization, personalized network configuration, and more, making it an ideal solution for teams who want to leverage all the benefits of Docker with minimal or no external dependencies.
Prerequisites
Before we begin with our tutorial, make sure you have the following:
- A VPS running Ubuntu 20.04 or later. Hostinger offers VPS hosting with a one-click Docker with Ubuntu template, starting at $4.49/month.
- A user account with sudo privileges.
- Docker and Docker Compose installed on the VPS.
- Nginx installed and configured.
- Certbot for SSL certificates.
- A domain or subdomain pointing to your VPS (optional but recommended for secure setup with HTTPS).
- Basic command line skills.
Setting up a private Docker registry
1. Install and configure a Docker registry
First off, on your VPS of choice and using the command line, create a dedicated folder for your project and access it:
mkdir ~/docker-registry cd docker-registry
Let’s also create three additional folders:
- a folder named data, where you will store your registry’s private images,
- a folder named auth, where credentials will be stored,
- a folder named nginx, where your Nginx configuration and SSL certificates will be stored.
mkdir data auth nginx
Now let’s pull the official Docker private registry image. You can pull it directly from Docker Hub by running the following command:
docker pull registry:2
To keep things clean, we will run the private registry container using Docker Compose. In your project folder, create a Docker compose file and open it using nano:
touch docker-compose.yml nano docker-compose.yml
Once in the file, add the following:
version: '3' services: registry: image: registry:2 ports: - "5000:5000" volumes: - ./data:/var/lib/registry
Here:
- image: registry:2: will pull the image if you haven’t yet done so separately,
- ports: – “5000:5000”: will map the container’s port 5000 to your VPS’s port 5000,
- volumes: – ./data:/var/lib/registry: will mount a volume to store Docker image layers.
Now you can start the configuration:
docker-compose up -d
Check that your Docker registry container is up by running:
docker-compose ps
Alternatively, you can use curl:
curl http://localhost:5000/v2/
If everything is running smoothly, this should return:
{}
2. Set up the Nginx reverse proxy
Let’s ensure Nginx is installed correctly:
sudo systemctl status nginx
If not, run the following to install it on your VPS:
sudo apt update sudo apt install nginx -y
Configure Nginx as a reverse proxy
Next, create a config file for Nginx:
sudo nano /etc/nginx/sites-available/docker-registry
And add the following:
server { listen 80; server_name your-domain.com; # Add your domain here or leave as _ if no domain, or localhost location / { proxy_pass http://localhost: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; } }
Note that HTTP forwarding to your domain name won’t work properly without a domain.
Now you can enable the config:
sudo ln -s /etc/nginx/sites-available/docker-registry /etc/nginx/sites-enabled/
And test and reload Nginx:
sudo nginx -t sudo systemctl reload nginx
You can also test for proper forwarding by running:
curl http://your-vps-ip/v2/_catalog
You should get a JSON response listing the repositories in your own private Docker registry.
Configure Nginx to handle HTTPS requests for the registry.
For this step to be successful, you’ll need:
- Your Docker registry already running on your VPS at localhost:5000,
- A domain or subdomain pointing to your VPS IP,
- Your Nginx config enabled,
- Certbot.
Let’s begin by installing Certbot:
sudo apt update sudo apt install certbot python3-certbot-nginx
Now, we have to obtain an SSL certificate using Certbot:
sudo certbot --nginx -d registry.yourdomain.com
This will edit your Nginx config with SSL settings and create a secure HTTPS server block.
In your config file, you can expect to see this:
… server { listen 443 ssl; server_name registry.yourdomain.com; ssl_certificate /etc/letsencrypt/live/registry.yourdomain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/registry.yourdomain.com/privkey.pem; …
As before, test and reload Nginx to conclude and ensure everything is up to date.
sudo nginx -t sudo systemctl reload nginx
Finally, use curl again to test that the forwarding works:
curl -k https://registry.yourdomain.com/v2/_catalog
You should get a JSON object such as:
{"repositories": [ ]}
If your run fails, make sure your private registry is running and that ports 80 and 443 are not blocked by your firewall.
3. Increase file upload size in Nginx
Increasing file upload size might be beneficial and even necessary if you are using larger Docker images, as Nginx’s default client_max_body_size is only 1 MB.
Adjusting upload size is pretty straightforward. For example, let’s imagine we want to set the limit to 2 GB. Open the Nginx config file again and add the following:
server { … client_max_body_size 2G … }
As usual, test and reload Nginx to make sure your changes are applied.
sudo nginx -t sudo systemctl reload nginx
In order to verify the new upload size limit, push an image to your registry that is larger than the default size of 1 MB:
docker push registry.yourdomain.com/your-image
4. Authenticate users for the private registry
Setting up authentication for your private registry is crucial to keeping your private images safe. With Nginx, you can set up HTTP authentication and create users with ad hoc credentials, thus deciding who has access to your Docker registry.
Let’s do this by setting up the htpasswd utility within an authentication file and adding the accepted users and passwords. In order to obtain htpasswd, you’ll need to install the apache2-utils package:
sudo apt install apache2-utils -y
You can now create an .htpasswd file as well as your very first user. Be sure to do this within your project directory:
mkdir -p ./auth sudo htpasswd -c ./auth/htpasswd username
To add new users, run the same command, without including -c.
Configure basic authentication
To configure Nginx to require authentication, open your Nginx site config (e.g., /etc/nginx/sites-available/docker-registry) and add the following inside your location / block:
location / { … auth_basic "Docker Registry Authentication"; auth_basic_user_file /full/path/to/docker-registry/auth/htpasswd; … }
Replace /full/path/to/docker-registry/auth/htpasswd with the absolute path to your .htpasswd file on your VPS.
Now update your Docker compose file to include authentication:
version: '3' services: registry: image: registry:2 ports: - "5000:5000" environment: - REGISTRY_AUTH=htpasswd - REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm - REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd volumes: - ./data:/var/lib/registry - ./auth:/auth
Restart your container to apply the changes, like so:
docker-compose down docker-compose up -d
5. Configure Docker clients to trust the private registry
You must specifically set up Docker to trust your registry if it is not using a valid TLS/SSL certificate (i.e., HTTP or a self-signed cert); otherwise, Docker will prevent any communication with it.
If you’re using HTTPS with a valid certificate from Let’s Encrypt or similar, you can skip this step.
Update the Docker daemon
First off, open your daemon file.
sudo nano /etc/docker/daemon.json
If it doesn’t exist, you can create it with:
touch /etc/docker/daemon.json
Add the following to the JSON object:
{ … "insecure-registries": ["your.domain:5000"] … }
If needed, you can replace your.registry.domain:5000 with:
- Your VPS public IP and port (such as 123.123.123.123:5000), or
- A domain or a subdomain that points to your VPS (most common scenario)
- localhost:5000 or 127.0.0.1:5000, if the Docker registry is on the same VPS (less common scenario)
Important note: use this only if you’re using HTTP or self-signed HTTPS certs.
Make sure to restart Docker to apply your changes:
sudo systemctl restart docker
6. Push and pull images from the private registry
At this point, your registry should be running reliably and your clients should be able to trust it. You can now store and retrieve images securely. Below are a few additional tips.
Log in to the registry via Docker CLI
As we previously set up authentication, your registry will request that you log in. If you have not enabled authentication, Docker will skip this step for you. To log in, run:
docker login your.registry.domain:5000
Where your.registry.domain:5000 is whatever domain, subdomain, or VPS public IP and port you have chosen.
At this point, you’ll be prompted to input the password and username you created. Docker will save your login information locally in ~/.docker/config.json if the login is successful.
Tag an image for the private registry
To correctly deliver a desired image to your private registry, you need to tag it with the full registry address. You can do this by running the following:
docker tag ubuntu:latest your.registry.domain:5000/my-image:my-tag
Where my-image is the name of your image, and my-tag is the tag you want to assign to it. If you omit the tag (i.e., :my-tag), Docker will default to using :latest.
Push and pull tagged images to the private registry
After you are done tagging, your image is ready to be pushed to your Docker registry. At this stage, Docker will authenticate you (if authentication has been set up), connect to the registry, and upload your image layers:
docker push your.registry.domain:5000/my-image
To pull the image, simply use the pull command:
docker pull your.registry.domain:5000/my-image
Docker will look for your image in the registry, cache it locally for usage, and pull it. Note that for this step to be successful, your registry needs to be running.
Interacting with images in your Docker registry: example
Let’s take a look at a comprehensive example of how to use your private registry.
# Tag your image docker tag nginx:latest your.registry.domain:5000/my-image # Login if needed docker login your.registry.domain:5000 # Push your image docker push your.registry.domain:5000/my-image # On another machine (pointing to the same registry) docker pull your.registry.domain:5000/my-image # Browse what’s available in the registry curl http://your.registry.domain:5000/v2/_catalog
Conclusion
In this guide, we have seen how to set up a private Docker registry to gain full control over those container images you wish to keep private. This also enhances security, keeping your images safe and distributing them with reduced risk, and reduces latency, making for a better development experience both in testing and production environments.
By following the steps outlined in this guide, you can build a secure and efficient foundation for privately managing your container images and scaling your projects without worrying about security.
Docker private registry FAQ
What is a private Docker registry?
A private Docker registry is a local or cloud-hosted service that allows you to store and manage your Docker images privately and securely, and keep them within your own infrastructure or VPS.
How do I set up a private Docker registry?
You can deploy a private registry by launching the Docker Registry container, adding reverse proxy support with Nginx, and enabling HTTPS. You can also secure it with basic authentication.
What’s the difference between a public and private Docker registry?
The key differences are visibility and access. Private registries are self-hosted and secure, used internally within your team or organization. Public registries are shared platforms where images can be downloaded by anyone.