{"id":128735,"date":"2025-05-22T18:53:20","date_gmt":"2025-05-22T18:53:20","guid":{"rendered":"\/tutorials\/?p=128735"},"modified":"2025-05-26T05:45:44","modified_gmt":"2025-05-26T05:45:44","slug":"docker-private-registry","status":"publish","type":"post","link":"\/tutorials\/docker-private-registry","title":{"rendered":"How to set up and use a private Docker registry\u00a0"},"content":{"rendered":"<p>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&rsquo;t want to expose publicly, a public registry like Docker Hub might not cut it.<\/p><p>In this guide, we&rsquo;ll learn how to set up and use Docker private registries for effective and secure Docker image storage and management.<\/p><p>\n\n\n\n\n\n\n<\/p><h2 class=\"wp-block-heading\" id=\"h-what-is-a-private-docker-registry\">What is a private Docker registry?<\/h2><p>A private registry offers a secure and centralized location for Docker images, which gives developers control over who can access and manage them.<\/p><p>This offers a number of benefits:<\/p><ul class=\"wp-block-list\">\n<li><strong>Reduced dependency on third-party services<\/strong> for more control in the event of outages, disruptions, or breaches.<\/li>\n\n\n\n<li><strong>Easy access to images and versioning<\/strong>, so you can push and pull your images like code.<\/li>\n\n\n\n<li><strong>Faster performance<\/strong> thanks to centralized hosting.<\/li>\n\n\n\n<li><strong>Better customization<\/strong>, as you get full control over the configuration of your private registry.<\/li>\n\n\n\n<li><strong>Improved security<\/strong>, as access is restricted to authorized users.<\/li>\n<\/ul><p>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.<\/p><h2 class=\"wp-block-heading\" id=\"h-prerequisites\">Prerequisites<\/h2><p>Before we begin with our tutorial, make sure you have the following:<\/p><ul class=\"wp-block-list\">\n<li>A VPS running Ubuntu 20.04 or later. Hostinger offers <a href=\"\/vps\/docker-hosting\">VPS hosting<\/a> with a one-click Docker with Ubuntu template, starting at <strong>$6.49\/month<\/strong>.<\/li>\n\n\n\n<li>A user account with <strong>sudo<\/strong> privileges.<\/li>\n\n\n\n<li><a href=\"\/tutorials\/how-to-install-docker-on-ubuntu\">Docker<\/a> and Docker Compose installed on the VPS.<\/li>\n\n\n\n<li><a href=\"\/tutorials\/nginx-redirect\">Nginx<\/a> installed and configured.<\/li>\n\n\n\n<li><a href=\"https:\/\/certbot.eff.org\/\" target=\"_blank\" rel=\"noopener\">Certbot<\/a> for SSL certificates.<\/li>\n\n\n\n<li>A domain or subdomain pointing to your VPS (optional but recommended for secure setup with HTTPS).<\/li>\n\n\n\n<li>Basic command line skills.<\/li>\n<\/ul><figure class=\"wp-block-image size-large\"><a class=\"hgr-tutorials-cta hgr-tutorials-cta-vps-hosting\" href=\"\/vps-hosting\" target=\"_blank\" rel=\"noreferrer noopener\"><img decoding=\"async\" width=\"1024\" height=\"300\" src=\"https:\/\/imagedelivery.net\/LqiWLm-3MGbYHtFuUbcBtA\/wp-content\/uploads\/sites\/2\/2023\/02\/VPS-hosting-banner.png\/public\" alt=\"\" class=\"wp-image-77934\" srcset=\"https:\/\/imagedelivery.net\/LqiWLm-3MGbYHtFuUbcBtA\/wp-content\/uploads\/sites\/2\/2023\/02\/VPS-hosting-banner.png\/w=1024,fit=scale-down 1024w, https:\/\/imagedelivery.net\/LqiWLm-3MGbYHtFuUbcBtA\/wp-content\/uploads\/sites\/2\/2023\/02\/VPS-hosting-banner.png\/w=300,fit=scale-down 300w, https:\/\/imagedelivery.net\/LqiWLm-3MGbYHtFuUbcBtA\/wp-content\/uploads\/sites\/2\/2023\/02\/VPS-hosting-banner.png\/w=150,fit=scale-down 150w, https:\/\/imagedelivery.net\/LqiWLm-3MGbYHtFuUbcBtA\/wp-content\/uploads\/sites\/2\/2023\/02\/VPS-hosting-banner.png\/w=768,fit=scale-down 768w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure><h2 class=\"wp-block-heading\" id=\"h-setting-up-a-private-docker-registry\">Setting up a private Docker registry<\/h2><h3 class=\"wp-block-heading\" id=\"h-1-install-and-configure-a-docker-registry\">1. Install and configure a Docker registry<\/h3><p>First off, on your VPS of choice and using the command line, create a dedicated folder for your project and access it:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">mkdir ~\/docker-registry\n\ncd docker-registry<\/pre><p>Let&rsquo;s also create three additional folders:<\/p><ul class=\"wp-block-list\">\n<li>a folder named <strong>data<\/strong>, where you will store your registry&rsquo;s private images,&nbsp;<\/li>\n\n\n\n<li>a folder named <strong>auth<\/strong>, where credentials will be stored,&nbsp;<\/li>\n\n\n\n<li>a folder named <strong>nginx<\/strong>, where your Nginx configuration and SSL certificates will be stored.<\/li>\n<\/ul><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">mkdir data auth nginx<\/pre><p>Now let&rsquo;s pull the official Docker private registry image. You can pull it directly from Docker Hub by running the following command:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">docker pull registry:2<\/pre><p>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 <strong>nano<\/strong>:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">touch docker-compose.yml\n\nnano docker-compose.yml<\/pre><p>Once in the file, add the following:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">version: '3'\n\nservices:\n\n&nbsp;&nbsp;registry:\n\nimage: registry:2\n\nports:\n\n&nbsp;&nbsp; - \"5000:5000\"\n\nvolumes:\n\n&nbsp;&nbsp; - .\/data:\/var\/lib\/registry<\/pre><p>Here:<\/p><ul class=\"wp-block-list\">\n<li><strong>image: registry:2:<\/strong> will pull the image if you haven&rsquo;t yet done so separately,<\/li>\n\n\n\n<li><strong>ports: &ndash; &ldquo;5000:5000&rdquo;:<\/strong> will map the container&rsquo;s port 5000 to your VPS&rsquo;s port 5000,<\/li>\n\n\n\n<li><strong>volumes: &ndash; .\/data:\/var\/lib\/registry:<\/strong> will mount a volume to store Docker image layers.<\/li>\n<\/ul><p>Now you can start the configuration:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">docker-compose up -d<\/pre><p>Check that your <a href=\"\/tutorials\/docker-start-a-container\/\">Docker registry container<\/a> is up by running:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">docker-compose ps<\/pre><p>Alternatively, you can use <a href=\"\/tutorials\/curl-command\">curl<\/a>:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">curl http:\/\/localhost:5000\/v2\/<\/pre><p>If everything is running smoothly, this should return:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">{}<\/pre><h3 class=\"wp-block-heading\" id=\"h-2-set-up-the-nginx-reverse-proxy\">2. Set up the Nginx reverse proxy<\/h3><p>Let&rsquo;s ensure <a href=\"\/tutorials\/how-to-set-up-nginx-reverse-proxy\/\">Nginx is installed<\/a> correctly:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">sudo systemctl status nginx<\/pre><p>If not, run the following to install it on your VPS:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">sudo apt update\n\nsudo apt install nginx -y<\/pre><p><strong>Configure Nginx as a reverse proxy<\/strong><\/p><p>Next, create a config file for Nginx:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">sudo nano \/etc\/nginx\/sites-available\/docker-registry<\/pre><p>And add the following:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">server {\n\nlisten 80;\n\nserver_name your-domain.com;&nbsp; # Add your domain here or leave as _ if no domain, or localhost\n\nlocation \/ {\n\n&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; &nbsp; proxy_pass http:\/\/localhost:5000;\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; &nbsp; proxy_set_header Host $host;\n\n&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; &nbsp; proxy_set_header X-Real-IP $remote_addr;\n\n&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; &nbsp; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n\n&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; &nbsp; proxy_set_header X-Forwarded-Proto $scheme;\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}\n\n}<\/pre><p>Note that HTTP forwarding to your domain name won&rsquo;t work properly without a domain.<\/p><p>Now you can enable the config:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">sudo ln -s \/etc\/nginx\/sites-available\/docker-registry \/etc\/nginx\/sites-enabled\/<\/pre><p>And test and reload Nginx:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">sudo nginx -t\n\nsudo systemctl reload nginx<\/pre><p>You can also test for proper forwarding by running:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">curl http:\/\/your-vps-ip\/v2\/_catalog<\/pre><p>You should get a JSON response listing the repositories in your own private Docker registry.<\/p><p><strong>Configure Nginx to handle HTTPS requests for the registry.<\/strong><\/p><p>For this step to be successful, you&rsquo;ll need:<\/p><ul class=\"wp-block-list\">\n<li>Your Docker registry already running on your VPS at <strong>localhost:5000<\/strong>,<\/li>\n\n\n\n<li>A domain or subdomain pointing to your VPS IP,<\/li>\n\n\n\n<li>Your Nginx config enabled,<\/li>\n\n\n\n<li>Certbot.<\/li>\n<\/ul><p>Let&rsquo;s begin by installing Certbot:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">sudo apt update\n\nsudo apt install certbot python3-certbot-nginx<\/pre><p>Now, we have to obtain an SSL certificate using Certbot:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">sudo certbot --nginx -d registry.yourdomain.com<\/pre><p>This will edit your Nginx config with SSL settings and create a secure HTTPS server block.<\/p><p>In your config file, you can expect to see this:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">&hellip;\n\nserver {\n\nlisten 443 ssl;\n\nserver_name registry.yourdomain.com;\n\nssl_certificate \/etc\/letsencrypt\/live\/registry.yourdomain.com\/fullchain.pem;\n\nssl_certificate_key \/etc\/letsencrypt\/live\/registry.yourdomain.com\/privkey.pem;\n\n&hellip;<\/pre><p>As before, test and reload Nginx to conclude and ensure everything is up to date.<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">sudo nginx -t\n\nsudo systemctl reload nginx<\/pre><p>Finally, use curl again to test that the forwarding works:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">curl -k https:\/\/registry.yourdomain.com\/v2\/_catalog<\/pre><p>You should get a JSON object such as:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">{\"repositories\": [ ]}<\/pre><p>If your run fails, make sure your private registry is running and that ports 80 and 443 are not blocked by your firewall.<\/p><h3 class=\"wp-block-heading\" id=\"h-3-increase-file-upload-size-in-nginx\">3. Increase file upload size in Nginx<\/h3><p>Increasing file upload size might be beneficial and even necessary if you are using larger Docker images, as Nginx&rsquo;s default <strong>client_max_body_size<\/strong> is only 1 MB.<\/p><p>Adjusting upload size is pretty straightforward. For example, let&rsquo;s imagine we want to set the limit to 2 GB. Open the Nginx config file again and add the following:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">server {\n\n&hellip;\n\n&nbsp;&nbsp;&nbsp;&nbsp; client_max_body_size 2G\n\n&hellip;\n\n}<\/pre><p>As usual, test and reload Nginx to make sure your changes are applied.<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">sudo nginx -t\n\nsudo systemctl reload nginx<\/pre><p>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:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">docker push registry.yourdomain.com\/your-image<\/pre><h3 class=\"wp-block-heading\" id=\"h-4-authenticate-users-for-the-private-registry\">4. Authenticate users for the private registry<\/h3><p>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.<\/p><p>Let&rsquo;s do this by setting up the <strong>htpasswd<\/strong> utility within an authentication file and adding the accepted users and passwords. In order to obtain <strong>htpasswd<\/strong>, you&rsquo;ll need to install the apache2-utils package:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">sudo apt install apache2-utils -y<\/pre><p>You can now create an <strong>.htpasswd<\/strong> file as well as your very first user. Be sure to do this within your project directory:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">mkdir -p .\/auth\n\nsudo htpasswd -c .\/auth\/htpasswd username<\/pre><p>To add new users, run the same command, without including <strong>-c<\/strong>.<\/p><p><strong>Configure basic authentication<\/strong><\/p><p>To configure Nginx to require authentication, open your Nginx site config (e.g., <strong>\/etc\/nginx\/sites-available\/docker-registry<\/strong>) and add the following inside your <strong>location \/ <\/strong>block:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">location \/ {\n\n&hellip;\n\nauth_basic \"Docker Registry Authentication\";\n\nauth_basic_user_file \/full\/path\/to\/docker-registry\/auth\/htpasswd;\n\n&hellip;\n\n}<\/pre><p>Replace <strong>\/full\/path\/to\/docker-registry\/auth\/htpasswd<\/strong> with the absolute path to your .htpasswd file on your VPS.<\/p><p>Now update your Docker compose file to include authentication:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">version: '3'\n\nservices:\n\n&nbsp;&nbsp;registry:\n\nimage: registry:2\n\nports:\n\n&nbsp;&nbsp; - \"5000:5000\"\n\nenvironment:\n\n&nbsp;&nbsp; - REGISTRY_AUTH=htpasswd\n\n&nbsp;&nbsp; - REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm\n\n&nbsp;&nbsp; - REGISTRY_AUTH_HTPASSWD_PATH=\/auth\/htpasswd\n\nvolumes:\n\n&nbsp;&nbsp; - .\/data:\/var\/lib\/registry\n\n&nbsp;&nbsp; - .\/auth:\/auth<\/pre><p>Restart your container to apply the changes, like so:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">docker-compose down\n\ndocker-compose up -d<\/pre><h3 class=\"wp-block-heading\" id=\"h-5-configure-docker-clients-to-trust-the-private-registry\">5. Configure Docker clients to trust the private registry<\/h3><p>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.<\/p><p>If you&rsquo;re using HTTPS with a valid certificate from Let&rsquo;s Encrypt or similar, you can skip this step.<\/p><p><strong>Update the Docker daemon<\/strong><\/p><p>First off, open your daemon file.<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">sudo nano \/etc\/docker\/daemon.json<\/pre><p>If it doesn&rsquo;t exist, you can create it with:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">touch \/etc\/docker\/daemon.json<\/pre><p>Add the following to the JSON object:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">{\n\n&hellip;\n\n&nbsp;&nbsp;\"insecure-registries\": [\"your.domain:5000\"]\n\n&hellip;\n\n}<\/pre><p>If needed, you can replace <strong>your.registry.domain:5000<\/strong> with:<\/p><ul class=\"wp-block-list\">\n<li>Your VPS public IP and port (such as 123.123.123.123:5000), or<\/li>\n\n\n\n<li>A domain or a subdomain that points to your VPS (most common scenario)<\/li>\n\n\n\n<li>localhost:5000 or 127.0.0.1:5000, if the Docker registry is on the same VPS (less common scenario)<\/li>\n<\/ul><p><strong>Important note:<\/strong> use this only if you&rsquo;re using HTTP or self-signed HTTPS certs.<\/p><p>Make sure to restart Docker to apply your changes:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">sudo systemctl restart docker<\/pre><h3 class=\"wp-block-heading\" id=\"h-6-push-and-pull-images-from-the-private-registry\">6. Push and pull images from the private registry<\/h3><p>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.<\/p><p><strong>Log in to the registry via Docker CLI<\/strong><\/p><p>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:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">docker login your.registry.domain:5000<\/pre><p>Where <strong>your.registry.domain:5000<\/strong> is whatever domain, subdomain, or VPS public IP and port you have chosen.<\/p><p>At this point, you&rsquo;ll be prompted to input the password and username you created. Docker will save your login information locally in <strong>~\/.docker\/config.json<\/strong> if the login is successful.<\/p><p><strong>Tag an image for the private registry<\/strong><\/p><p>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:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">docker tag ubuntu:latest your.registry.domain:5000\/my-image:my-tag<\/pre><p>Where <strong>my-image<\/strong> is the name of your image, and <strong>my-tag<\/strong> is the tag you want to assign to it. If you omit the tag (i.e., <strong>:my-tag<\/strong>), Docker will default to using <strong>:latest<\/strong>.<\/p><p><strong>Push and pull tagged images to the private registry<\/strong><\/p><p>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:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">docker push your.registry.domain:5000\/my-image<\/pre><p>To pull the image, simply use the <strong>pull<\/strong> command:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">docker pull your.registry.domain:5000\/my-image<\/pre><p>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.<\/p><p><strong>Interacting with images in your Docker registry: example<\/strong><\/p><p>Let&rsquo;s take a look at a comprehensive example of how to use your private registry.<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># Tag your image\n\ndocker tag nginx:latest your.registry.domain:5000\/my-image\n\n# Login if needed\n\ndocker login your.registry.domain:5000\n\n# Push your image\n\ndocker push your.registry.domain:5000\/my-image\n\n# On another machine (pointing to the same registry)\n\ndocker pull your.registry.domain:5000\/my-image\n\n# Browse what&rsquo;s available in the registry\n\ncurl http:\/\/your.registry.domain:5000\/v2\/_catalog<\/pre><div class=\"wp-block-image\">\n<figure data-wp-context='{\"imageId\":\"69dba2404125a\"}' data-wp-interactive=\"core\/image\" class=\"aligncenter size-large wp-lightbox-container\"><img decoding=\"async\" width=\"1460\" height=\"261\" data-wp-class--hide=\"state.isContentHidden\" data-wp-class--show=\"state.isContentVisible\" data-wp-init=\"callbacks.setButtonStyles\" data-wp-on-async--click=\"actions.showLightbox\" data-wp-on-async--load=\"callbacks.setButtonStyles\" data-wp-on-async-window--resize=\"callbacks.setButtonStyles\" src=\"https:\/\/imagedelivery.net\/LqiWLm-3MGbYHtFuUbcBtA\/wp-content\/uploads\/sites\/2\/2025\/05\/docker-private-registry-image-example.jpg\/public\" alt=\"Create and push an image to your private registry in a few easy steps.\" class=\"wp-image-128741\" srcset=\"https:\/\/imagedelivery.net\/LqiWLm-3MGbYHtFuUbcBtA\/wp-content\/uploads\/sites\/2\/2025\/05\/docker-private-registry-image-example.jpg\/w=1460,fit=scale-down 1460w, https:\/\/imagedelivery.net\/LqiWLm-3MGbYHtFuUbcBtA\/wp-content\/uploads\/sites\/2\/2025\/05\/docker-private-registry-image-example.jpg\/w=300,fit=scale-down 300w, https:\/\/imagedelivery.net\/LqiWLm-3MGbYHtFuUbcBtA\/wp-content\/uploads\/sites\/2\/2025\/05\/docker-private-registry-image-example.jpg\/w=1024,fit=scale-down 1024w, https:\/\/imagedelivery.net\/LqiWLm-3MGbYHtFuUbcBtA\/wp-content\/uploads\/sites\/2\/2025\/05\/docker-private-registry-image-example.jpg\/w=150,fit=scale-down 150w, https:\/\/imagedelivery.net\/LqiWLm-3MGbYHtFuUbcBtA\/wp-content\/uploads\/sites\/2\/2025\/05\/docker-private-registry-image-example.jpg\/w=768,fit=scale-down 768w\" sizes=\"(max-width: 1460px) 100vw, 1460px\" \/><button class=\"lightbox-trigger\" type=\"button\" aria-haspopup=\"dialog\" aria-label=\"Enlarge\" data-wp-init=\"callbacks.initTriggerButton\" data-wp-on-async--click=\"actions.showLightbox\" data-wp-style--right=\"state.imageButtonRight\" data-wp-style--top=\"state.imageButtonTop\">\n\t\t\t<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"12\" height=\"12\" fill=\"none\" viewbox=\"0 0 12 12\">\n\t\t\t\t<path fill=\"#fff\" d=\"M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z\"><\/path>\n\t\t\t<\/svg>\n\t\t<\/button><\/figure><\/div><h2 class=\"wp-block-heading\" id=\"h-conclusion\">Conclusion<\/h2><p>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.<\/p><p>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.<\/p><h2 class=\"wp-block-heading\" id=\"h-docker-private-registry-faq\">Docker private registry FAQ<\/h2><div class=\"schema-faq wp-block-yoast-faq-block\"><div class=\"schema-faq-section\" id=\"faq-question-1747939569299\"><h3 class=\"schema-faq-question\">What is a private Docker registry?<\/h3> <p class=\"schema-faq-answer\">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.<\/p> <\/div> <div class=\"schema-faq-section\" id=\"faq-question-1747939574736\"><h3 class=\"schema-faq-question\">How do I set up a private Docker registry?<\/h3> <p class=\"schema-faq-answer\">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.<\/p> <\/div> <div class=\"schema-faq-section\" id=\"faq-question-1747939580369\"><h3 class=\"schema-faq-question\">What&rsquo;s the difference between a public and private Docker registry?<\/h3> <p class=\"schema-faq-answer\">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.<\/p> <\/div> <\/div>\n","protected":false},"excerpt":{"rendered":"<p>When working on complex projects with Docker, using Docker Hub is a convenient way to handle images. However, when storing [&#8230;]<\/p>\n<p><a class=\"btn btn-secondary understrap-read-more-link\" href=\"\/tutorials\/docker-private-registry\">Read More&#8230;<\/a><\/p>\n","protected":false},"author":536,"featured_media":121636,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"rank_math_title":"How to set up a Docker private registry on Ubuntu","rank_math_description":"In this tutorial, you will learn how to set up and use a Docker private registry on a VPS with secure configurations and practical tips.","rank_math_focus_keyword":"docker private registry","footnotes":""},"categories":[22646,22644],"tags":[],"class_list":["post-128735","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-pre-installed-applications","category-vps"],"hreflangs":[{"locale":"en-US","link":"https:\/\/www.hostinger.com\/tutorials\/docker-private-registry","default":0},{"locale":"en-CA","link":"https:\/\/www.hostinger.com\/ca\/tutorials\/docker-private-registry","default":0},{"locale":"en-UK","link":"https:\/\/www.hostinger.com\/uk\/tutorials\/docker-private-registry","default":0},{"locale":"en-PH","link":"https:\/\/www.hostinger.com\/ph\/tutorials\/docker-private-registry","default":0},{"locale":"en-MY","link":"https:\/\/www.hostinger.com\/my\/tutorials\/docker-private-registry","default":0},{"locale":"en-IN","link":"https:\/\/www.hostinger.com\/in\/tutorials\/docker-private-registry","default":0},{"locale":"en-AU","link":"https:\/\/www.hostinger.com\/au\/tutorials\/docker-private-registry","default":0},{"locale":"en-NG","link":"https:\/\/www.hostinger.com\/ng\/tutorials\/docker-private-registry","default":0}],"_links":{"self":[{"href":"https:\/\/www.hostinger.com\/tutorials\/wp-json\/wp\/v2\/posts\/128735","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.hostinger.com\/tutorials\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.hostinger.com\/tutorials\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.hostinger.com\/tutorials\/wp-json\/wp\/v2\/users\/536"}],"replies":[{"embeddable":true,"href":"https:\/\/www.hostinger.com\/tutorials\/wp-json\/wp\/v2\/comments?post=128735"}],"version-history":[{"count":4,"href":"https:\/\/www.hostinger.com\/tutorials\/wp-json\/wp\/v2\/posts\/128735\/revisions"}],"predecessor-version":[{"id":128868,"href":"https:\/\/www.hostinger.com\/tutorials\/wp-json\/wp\/v2\/posts\/128735\/revisions\/128868"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.hostinger.com\/tutorials\/wp-json\/wp\/v2\/media\/121636"}],"wp:attachment":[{"href":"https:\/\/www.hostinger.com\/tutorials\/wp-json\/wp\/v2\/media?parent=128735"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.hostinger.com\/tutorials\/wp-json\/wp\/v2\/categories?post=128735"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.hostinger.com\/tutorials\/wp-json\/wp\/v2\/tags?post=128735"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}