Docker Registry: Self-hosting a Secure and Efficient Private Registry

Docker tutorial - IT technology blog
Docker tutorial - IT technology blog

Introduction to Private Docker Registry

When working with Docker, we frequently build and package applications into Docker images. These images need to be stored so that other environments (such as development, staging, or production) can easily pull and use them.

Docker Hub is a popular public registry, ideal for open-source or public images. However, when developing internal applications, containing sensitive data, or not wanting to share images externally, using a Private Docker Registry becomes essential. A Private Registry gives you complete control over the storage and distribution of your Docker images.

Comparing Methods for Managing Docker Images

To store and manage Docker images, we have a few main options:

1. Docker Hub

Advantages:

  • Easy to Use: Getting started is very simple; it’s almost the default choice for all Docker users.
  • Free for Public Repos: You can store an unlimited number of public images.
  • Large Community: Easily find base images and popular libraries.

Disadvantages:

  • Limited Private Repos: Without a paid subscription, you only have a limited number of private repositories (currently 1).
  • Rate Limit: There’s a limit on the number of image pulls, for example, 100 pulls every 6 hours for anonymous users, which particularly affects unpaid users.
  • Third-party Dependency: You don’t have full control over your data.

2. Cloud Container Registry Services (AWS ECR, GCP GCR/Artifact Registry, Azure ACR)

Advantages:

  • Managed Service: The cloud provider handles everything from infrastructure, scaling, security, to backups.
  • Scalable: Automatically scales according to your usage needs.
  • Deep Integration: Tightly connected with other cloud services (CI/CD, Kubernetes).
  • Good Security: Often comes with advanced security features.

Disadvantages:

  • Cost: Can be quite high, especially when storing a lot of data or having high traffic.
  • Vendor Lock-in: More difficult when wanting to switch between cloud providers.
  • Provider Dependency: Still doesn’t offer full control like self-hosting.

3. Self-hosting a Private Docker Registry

Advantages:

  • Full Control: You own and fully control your image data.
  • Maximum Security: Can be deployed within an internal network, behind a firewall. This helps meet the most stringent security and compliance requirements.
  • Unlimited: Capacity and number of repositories depend only on your hardware and infrastructure.
  • Cost Optimization: At a large scale, self-hosting can be cheaper than cloud services.

Disadvantages:

  • Effort Intensive: Requires time and knowledge to install, configure, maintain, back up, and troubleshoot.
  • Knowledge Requirement: Requires understanding of Docker, networking, security, and system administration.

When Should You Self-Host a Private Docker Registry?

Based on the analysis above, I believe you should consider self-hosting a Private Docker Registry in the following cases:

  • High Security and Compliance Requirements: When you cannot store images on public services due to internal security reasons or legal regulations (such as GDPR, HIPAA).
  • Absolute Data Control: You want complete authority over where your image data is stored, who can access it, and how.
  • Cost Optimization for Large Scale: If you have a large number of images and a high volume of pull/push operations, self-hosting on existing infrastructure can significantly save bandwidth and storage costs compared to cloud services.
  • Internal/Offline Environment: When you need a registry to operate within an internal network without internet access or require extremely fast pull/push speeds between servers in the same datacenter.

I remember a while ago, my team had a production cluster running over 30 containers. At that time, pulling images from Docker Hub or even cloud registries was costly in terms of bandwidth and time. I decided to self-host a private registry directly on a local server. The result was a 40% reduction in resource usage for image pull/push operations compared to going through the Internet to external registries, while also significantly increasing deployment speed. It truly made a big difference in system performance.

Guide to Deploying a Secure Private Docker Registry

Now, let’s dive into the steps for deploying a Private Docker Registry with HTTPS security and user authentication. I will provide detailed step-by-step instructions for you to easily follow.

Step 1: Prepare the Environment

You will need a Linux server (Ubuntu or CentOS both work) with Docker installed. I recommend using Docker Compose for easier service management.

1.1. Install Docker (if not already installed):


sudo apt update
sudo apt install apt-transport-https ca-certificates curl gnupg lsb-release
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-compose-plugin

# Add user to docker group to avoid using sudo
sudo usermod -aG docker $USER
newgrp docker

1.2. Install Docker Compose (v2):

If you installed Docker following the instructions above, the Docker Compose Plugin (v2) is already installed. You just need to type docker compose (with a space) instead of docker-compose.

Step 2: Run a Basic Docker Registry (HTTP – Insecure)

First, we will run a basic Registry over HTTP. This is just an initial step for testing and should not be used for production due to lack of security.

2.1. Create a data storage directory:

The Registry will store images on disk. You should map a directory on the host to the container so that data is not lost when the container stops or is deleted.


sudo mkdir -p /var/lib/registry

2.2. Run the Registry container:


docker run -d -p 5000:5000 --restart=always --name registry -v /var/lib/registry:/var/lib/registry registry:2

This command will pull the registry:2 image (the most stable version of Docker Registry), run it on port 5000, and map the host’s /var/lib/registry directory into the container.

2.3. Configure Docker daemon to allow insecure registry (for testing only):

By default, the Docker daemon only allows HTTPS connections. To test with HTTP, you need to edit the /etc/docker/daemon.json file.


sudo nano /etc/docker/daemon.json

Add or modify the content as follows (replace your_server_ip:5000 with your IP or domain):


{
  "insecure-registries": [
    "your_server_ip:5000"
  ]
}

Then, restart the Docker service:


sudo systemctl restart docker

2.4. Test Push/Pull an image:


docker pull hello-world
docker tag hello-world your_server_ip:5000/hello-world
docker push your_server_ip:5000/hello-world
docker rmi your_server_ip:5000/hello-world hello-world # Delete local image to test pull
docker pull your_server_ip:5000/hello-world

If everything works, you will see the hello-world image pushed and pulled successfully. Don’t forget to remove insecure-registries from daemon.json and restart Docker after completing this test step.

Step 3: Secure with HTTPS (Nginx Reverse Proxy + Let’s Encrypt)

This is a crucial step to ensure the security of your Private Registry. We will use Nginx as a reverse proxy and Let’s Encrypt to issue free SSL certificates.

3.1. Prepare Domain and DNS:

You need a domain name (e.g., registry.yourdomain.com) and point its A (or CNAME) record to your server’s public IP address.

3.2. Stop old Registry and prepare Docker Compose:

Stop and remove the old registry container:


docker stop registry
docker rm registry

Create docker-compose.yml file:


version: '3.8'

services:
  registry:
    image: registry:2
    volumes:
      - /var/lib/registry:/var/lib/registry
    environment:
      REGISTRY_HTTP_ADDR: 0.0.0.0:5000
    restart: always

Note: We are no longer exposing port 5000 to the host; Nginx will handle that.

3.3. Install Nginx:


sudo apt update
sudo apt install nginx

3.4. Install Certbot and obtain SSL certificate (Let’s Encrypt):


sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d registry.yourdomain.com

Certbot will automatically configure Nginx for you and renew the certificate.

3.5. Configure Nginx as a Reverse Proxy:

Your Nginx configuration file for the domain will be located at /etc/nginx/sites-available/registry.yourdomain.com (or similar), already created by Certbot. Please check and edit if necessary, ensuring it forwards requests to port 5000 of the registry container.

A basic configuration will look like this (Certbot will automatically add the SSL part):


server {
    listen 80;
    listen [::]:80;
    server_name registry.yourdomain.com;

    location / {
        # Redirect HTTP to HTTPS
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    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;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    client_max_body_size 0; # Allow large image uploads

    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;
        proxy_read_timeout 900s; # Increase timeout for large push/pull operations

        # Required headers for Docker Registry
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-Server $host;
        proxy_buffering off;
        chunked_transfer_encoding on;
    }
}

Test Nginx configuration and restart:


sudo nginx -t
sudo systemctl restart nginx

3.6. Run Registry with Docker Compose:

Go into the directory containing docker-compose.yml and run:


docker compose up -d

Your Registry is now running over HTTPS. You can test push/pull again, this time using your domain and without needing --insecure-registry.


docker pull hello-world
docker tag hello-world registry.yourdomain.com/hello-world
docker push registry.yourdomain.com/hello-world

Step 4: Add User Authentication (Basic Authentication)

To prevent unauthorized access, we will add Basic Authentication to the Registry.

4.1. Install apache2-utils (to use htpasswd):


sudo apt install apache2-utils

4.2. Create htpasswd file:

Create a file containing user and password information (e.g., /etc/nginx/conf.d/registry.password). Use -c for the first creation; for subsequent new users, omit -c.


sudo htpasswd -c /etc/nginx/conf.d/registry.password your_username

Enter the password when prompted.

4.3. Configure Nginx to require authentication:

Edit the Nginx configuration file for the Registry (/etc/nginx/sites-available/registry.yourdomain.com), add the following two lines inside the location / {} block of the HTTPS server:


    auth_basic "Registry Restricted";
    auth_basic_user_file /etc/nginx/conf.d/registry.password;

Then, test the Nginx configuration and restart:


sudo nginx -t
sudo systemctl restart nginx

4.4. Log in to the Registry:

Now when you push/pull, Docker will ask you to log in:


docker login registry.yourdomain.com

Enter the username and password you created using htpasswd. After successful login, you can push/pull images as usual.

Step 5: Manage Storage (Persistent Volume)

We configured the /var/lib/registry volume in Step 2. It’s important to ensure this directory has enough space and is regularly backed up. If you’re running on a VPS, make sure this volume is mounted on a highly durable and recoverable disk.

Step 6: Optimization and Maintenance

Self-hosting a Registry isn’t just about setup, but also includes regular maintenance:

  • Garbage Collection: When you delete an image from the Registry, its layers remain on disk. To free up space, you need to run the garbage collection command. However, make sure there are no push/pull operations in progress when running this command.

docker compose stop registry
docker run --rm -v /var/lib/registry:/var/lib/registry registry:2 garbage-collect /etc/docker/registry/config.yml
docker compose start registry

You may need to map the Registry’s configuration file if it doesn’t use the default.

  • Limit Network Access: Use a firewall (UFW, firewalld) to only allow specific IPs or IP ranges to access port 443 of the Registry.
  • Backup: Set up a regular backup plan for the /var/lib/registry directory to avoid data loss.

Conclusion

Self-hosting a Private Docker Registry provides flexibility, security, and high control over your Docker images. Although it requires some initial effort for setup and configuration, with this detailed guide, I believe you can successfully deploy a secure and efficient Registry. This is an optimal solution for projects that require information security, infrastructure optimization, and complete control over the application lifecycle.

Share: