DEV Community

Cover image for Private Docker Registry
Kirby Angell
Kirby Angell

Posted on

Private Docker Registry

Recently, as of this post, Docker Hub has raised their rates for their "Teams" platform. We were mostly using Docker Hub to host private images and made little use of "Teams". I can't say if it was an unreasonable rate hike or not, but it was enough for me to look at our usage compared to the new price. We have private images to load into containers on our servers. The software to host a Docker registry, the service Docker Hub provides, is itself available as a Docker image. So we have our own servers, why not our own registry?

I setup a registry for our company to use for its private images and here's how you can too. As a bonus I've included a simple web based UI you can use to examine what images are being hosted in your private registry.

In this post I will:

  1. Create a Docker compose file for our registry; and
  2. Configure the registry to proxy requests to Docker Hub for public images; and
  3. Create an NGINX configuration with SSL and password support to act as a proxy to our registry; and
  4. Demonstrate how to push and pull images from our private registry.

You will need to know how to do the following tasks that are not covered in this post:

  • Create, edit and deploy Docker containers from a compose YML file; the compose file can also be used in Portainer and probably other Docker management tools; and
  • Familiarity with NGINX configuration or some other proxy tool as it is not a good idea to directly expose your docker registry to the Internet.

compose.yml

Here is the compose.yml file we'll use to spin up our registry and UI. I'll go over some configuration you'll want to do for your particular environment below.

services:
  registry-ui:
    image: joxit/docker-registry-ui:main
    restart: always
    ports:
      - 80:80
    environment:
      - SINGLE_REGISTRY=true
      - REGISTRY_TITLE=Docker Registry UI
      - DELETE_IMAGES=true
      - NGINX_PROXY_PASS_URL=http://registry-server:5000
      - SHOW_CONTENT_DIGEST=true
      - SHOW_CATALOG_NB_TAGS=true
      - CATALOG_MIN_BRANCHES=1
      - CATALOG_MAX_BRANCHES=1
      - TAGLIST_PAGE_SIZE=100
      - REGISTRY_SECURED=false
      - CATALOG_ELEMENTS_LIMIT=1000
    container_name: registry-ui

  registry-server:
    image: registry:2
    restart: always
    ports:
      - "5000:5000"     
    volumes:
      - /var/lib/docker-registry:/var/lib/registry
    environment:
      - REGISTRY_STORAGE_DELETE_ENABLED=true
      - REGISTRY_PROXY_REMOTEURL=https://registry-1.docker.io
      - REGISTRY_PROXY_USERNAME= 
      - REGISTRY_PROXY_PASSWORD= 
    container_name: registry-server
Enter fullscreen mode Exit fullscreen mode

registry-ui

The principal change you'll need to make to this service is the port you want the container to listen on.

    ports:
      - 80:80
Enter fullscreen mode Exit fullscreen mode

If you want the UI to be available on a different port change the port number on the left side of the colon. If you want it to listen on port 8080 then you would change the ports specification to "8080:80".

You may also want to edit these options:

      - REGISTRY_TITLE=Docker Registry UI
      - DELETE_IMAGES=true
      - NGINX_PROXY_PASS_URL=http://registry-server:5000
Enter fullscreen mode Exit fullscreen mode

Here you can change the title of the web page displayed. I like to have an easy way to delete images so I have "DELETE_IMAGES" enabled, but you can set it to "false" if you don't want this feature. "NGINX_PROXY_PASS_URL" should be set to the name of your registry service ("registry-server" in my example) and the port it is listening on (5000).

You can find more information on the UI here.

registry-server

For the registry service, like the UI, you set the port you want the registry to listen on.

   ports:
      - "5000:5000"     
Enter fullscreen mode Exit fullscreen mode
    volumes:
      - /var/lib/docker-registry:/var/lib/registry
Enter fullscreen mode Exit fullscreen mode

You could use a persistent Docker volume to store the registry images, but instead I map a directory manually. This is just personal preference.

Environment

    environment:
      - REGISTRY_STORAGE_DELETE_ENABLED=true
      - REGISTRY_PROXY_REMOTEURL=https://registry-1.docker.io  
      - REGISTRY_PROXY_USERNAME= 
      - REGISTRY_PROXY_PASSWORD= 
Enter fullscreen mode Exit fullscreen mode

The first option turns on the ability to delete images from the registry. Simple enough, set it to "false" if you don't want your registry to allow this.

The rest of this section is entirely optional. If included your private docker registry will use the public Docker Hub registry to attempt to load any images you ask for that aren't in your registry. Doing this allows you to use the same process to load images for your containers whether they are your private images or public images.

You probably won't need to change "REGISTRY_PROXY_REMOTEURL"; it points to the Docker Hub registry as it is.

Docker Hub does throttle requests:
Throttling

If 10 per hour isn't enough for your setup then you can create a free tier Docker Hub account and then configure your private registry to pull using those credentials. That will give you 40 pulls per hour from Docker Hub.

      - REGISTRY_PROXY_USERNAME= 
      - REGISTRY_PROXY_PASSWORD= 
Enter fullscreen mode Exit fullscreen mode

"REGISTRY_PROXY_USERNAME" must be set to the user name of your Docker Hub account. "REGISTRY_PROXY_PASSWORD" can be set to your Docker Hub password but I don't recommend that. Instead go to your Account Settings and click "Personal Access Tokens".
Personal Access tokens
When you create a new token make sure you copy to the clipboard right then. You won't be able to see it later. Paste that token into the "REGISTRY_PROXY_PASSWORD" field.

A benefit of having your private registry act as a proxy for the Docker Hub registry is that your private registry will cache those images. So if you have a lot of containers to spin up that rely on public images only 1 pull from Docker Hub will be made for the 1st container and all the rest will get the cached version.

Start the Registry

You start the registry like you would any other docker compose project. From the directory where you have the compose.yml file:

# Depending on which version of docker you are using the command
# is:
docker compose up 
# or
docker-compose up
Enter fullscreen mode Exit fullscreen mode

You will need to be a member of the "docker" group or use "sudo" to start the containers.

If your containers start without errors, then you can CTRL-C to kill them and restart them in the background by adding "-d".

docker compose up -d
# or
docker-compose up -d
Enter fullscreen mode Exit fullscreen mode

Proxy the Registry Using NGINX

I recommend setting up NGINX or some other web proxy in front of your docker registry especially if you are already using it for other services you host. There are so many variations to NGINX configurations that I can't give you a stock, "this will work", for your setup. But this basic configuration works for me:

# Upstreams
upstream docker-registry {
   server 127.0.0.1:5000;
}


server  {
  listen 10000 ssl;
  server_name    myserver.com;

  ssl_protocols TLSv1.3 TLSv1.2;

  chunked_transfer_encoding on;
  gzip off;

  client_max_body_size 0;  
  proxy_buffering off;  
  proxy_request_buffering off;  
  proxy_max_temp_file_size 0;  

  #access_log /var/log/nginx/docker-registry-access.log;
  error_log /var/log/nginx/docker-registry-error.log;

  ssl_certificate /etc/letsencrypt/live/myserver.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/myserver.com/privkey.pem;

  location / {

        auth_basic "Registry Realm";
        auth_basic_user_file /etc/docker-registry-htpasswd;  

        proxy_pass http://docker-registry/;
        proxy_set_header Host $http_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;
  }
}
Enter fullscreen mode Exit fullscreen mode

This file goes in "/etc/nginx/sites-available/docker-registry", with a symbolic link in "/etc/nginx/sites-enabled/":

sudo ln /etc/nginx/sites-available/docker-registry /etc/nginx/sites-enabled/
Enter fullscreen mode Exit fullscreen mode

You will need to change a few lines:

server  {
  listen 10000 ssl;
  server_name    myserver.com;
  ssl_certificate /etc/letsencrypt/live/myserver.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/myserver.com/privkey.pem;
Enter fullscreen mode Exit fullscreen mode

Replace the listen port with the port you want your registry to run on. Replace "myserver.com" in all 3 places with the domain name you are using. If you are not using Let's Encrypt then replace the whole path with where you keep your SSL certificate.

    auth_basic "Registry Realm";
    auth_basic_user_file /etc/docker-registry-htpasswd;  
Enter fullscreen mode Exit fullscreen mode

This section adds password protection to your registry. It is using a standard htpasswd file. To create this file follow these steps (Debian based only):

sudo apt update
sudo apt install apache2-utils
htpasswd -c /etc/docker-registry-htpasswd username
Enter fullscreen mode Exit fullscreen mode

Replace "username" with whatever you want for a name. You will be prompted for a password. You can add additional users by repeating the command without the "-c".

After you've create the file and made the necessary changes, you can test your ngix configuration sanity using this command:

sudo nginx -t
Enter fullscreen mode Exit fullscreen mode

If the configuration is acceptable you'll see messages like these:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
Enter fullscreen mode Exit fullscreen mode

If the NGINX configuration test works you can restart NGINX using this command:

sudo systemctl restart nginx
Enter fullscreen mode Exit fullscreen mode

Using the Registry

To begin using your private registry you will need to login which creates the "~.docker/config.json" file. From the command line:

docker login myserver.com:PORT
Enter fullscreen mode Exit fullscreen mode

Replacing "myserver.com" and "port" with your values. You will prompted for the user name and password.

Now we're finally ready to use the registry! First we'll need to push an image to it:

docker push myserver.com:port/yourimage:tag
Enter fullscreen mode Exit fullscreen mode

Pull:

docker pull myserver.com:port/yourimage:tag
Enter fullscreen mode Exit fullscreen mode

Those two commands work for your private registry, but if you want to pull a public image from Docker Hub you will have to adjust the normal image path to include "/library/" like this:

docker pull myserver.com:port/library/publicimage:tag
Enter fullscreen mode Exit fullscreen mode

"/library" is the base path for all Docker Hub images.

Summary

In this post I've shown how to configure a private Docker registry. The registry uses Docker Hub as a backing store for public images. NGINX is used to provide SSL encryption and also password protect the private registry.

Top comments (0)