Setup a Private Docker Registry

 
 

Setting up a private Docker registry locally for our organisation or maybe for personal use can be seen as a must if we don’t want to expose publicly our custom Docker images. It is true that we can easily get a private repository if we would go for a paid subscription to one of the providers available out there on the market that are offering such services, but why doing so when we can simply do it by ourselves, internally, completely isolated and being under our control. Having a private Docker repository / registry will give us the ability to store how many images we want, to easily tweak their configuration as we’d like, whenever we’d like. Also, we will have the advantage of getting significantly higher speeds for pushing and pulling our private Docker images by simply using our internal network. In this tutorial we will show you how easy is to setup an internal private Docker registry, running on Docker on top of a CentOS machine which can be a VM or bare metal, it is really up to you what OS you will be using, the principle behind the private registry remains the same.

Table of contents

Context
Prerequisite
CentOS update and base packages
Docker CE and NGiNX repo files
Docker and NGiNX setup
Start Docker and NGiNX services
Create docker-compose file
Start Docker Registry
NGiNX configuration for Docker registry
Test Docker Registry URL
Final notes

Context

Assuming that we are working on a new project, we have been asked to build and deploy a private Docker registry using our own infrastructure. The aim of this project is to have a completely isolated environment for our Docker images, minimising the risk of exposing potentially sensitive data like passwords or security tokens for example. Building a private registry for Docker from the scratch make perfect sense in this case, we will not only gain a better security level but we will improve the speed of pushing and pulling images by using the internal infrastructure already available, we can even avoid possible disruption due 3rd party unavailability, less probable but still a question mark.

Prerequisite

Before we jump into any configuration details we need to make sure that we have in place all items listed below:

  • A valid domain name
  • An SSL Certificate for our domain name, can be issued by Let’s Encrypt or can be a paid one
  • A CentOS server, being VM or bare metal

CentOS update and base packages

On our very first step with this tutorial we have to make sure that we are using latest software packages available, so a yum update will do this for us. This particular update step is not quite mandatory but it is always good to have our CentOS operating system up to date before any new installation. Using a single command line, like shown below, we will be able to update all our software packages, in the same time we will also install a few other required packages for our Docker registry setup:

yum update -y; yum install -y yum-utils device-mapper-persistent-data lvm2;

Docker CE and NGiNX repo files

Once all previous required base OS packages have been successfully updated and installed we can move to our next step where we will have to create two repository files, first repository file for Docker CE (Community Edition) and the second repository file for NGiNX. Both these .repo files will be later invoked and used for installing the relevant packages needed for our tutorial.

 

Docker CE .repo file for CentOS

We will be starting by getting first the Docker Community Edition .repo repository file for CentOS, so let’s execute the next command line our terminal window:

yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

NGiNX .repo file for CentOS

Unfortunately for NGiNX the .repo file have to be created manually, not really a difficult task as we will be using vi text editor for this. So, once again, let’s run the next command like shown below:

vi /etc/yum.repos.d/nginx.repo

Once nginx.repo file is open we will have to add the next lines:

[nginx]
name=nginx repo
baseurl=http://nginx.org/packages/centos/$releasever/$basearch/
gpgcheck=0
enabled=1

We can now save and close this file as we will be moving to our next step where we have to setup Docker and NGiNX.

Docker and NGiNX setup

Having both needed repository files in place, for Docker and respectively NGiNX, we can now start installing these packages. Once again, for this step we will be using yum package manager, with just a single command line we will be installing docker-ce, docker-compose and nginx as shown in the example below:

yum install -y docker-ce docker-compose nginx

Start Docker and NGiNX services

Once all above packages were successfully installed and no errors were shown on our terminal window we can move on to enable and start both these main services, we can do this with just a single command line as shown here:

systemctl enable docker && systemctl enable nginx && systemctl start docker && systemctl start nginx

Create docker-compose file

We can now say that we are done with Docker and NGiNX services setup, next we will be focusing on creating our private Docker registry. In this particular tutorial, as you have already noticed, we will be using docker-compose in order to set up the Docker registry so let’s create a file called docker-registry.yml like in the example below:

cd ~; mkdir docker-registry; cd docker-registry; vi docker-registry.yml;

Here, we have to define our docker registry configuration settings, docker-registry.yml file will contain a basic description of our registry covering things like service type, what Docker registry image should be used for this setup, advertising ports and also the storage volumes that will be used for images:

#
# Reference: Tufora.com
#

version: '3'

services:
  registry:
    restart: always
    image: registry:latest
    ports:
    - "5000:5000"
    environment:
      REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /data
    volumes:
      - /mnt/registry:/data

As you can see, we have defined /mnt/registry as the main storage location for our future Docker images, this is a secondary disk attached to our system designed to serve as the default storage location for all our images.

Please feel free and adjust all the above values so they can match to your specific use case if you want so.

Start Docker Registry

Once everything is in place and properly configured then we can simply run docker-compose in order to start our private Docker registry locally, in the background by using -d flag:

docker-compose up -d

We are now done with the first part of our tutorial, we have managed to install all required packages for our private Docker registry and to start this using docker-compose, on the next step we will have to configure NGiNX service, without too many introductions let’s jump straight to the next step.

NGiNX configuration for Docker registry

This is the second and last part of our tutorial where we will be setting up NGiNX, this will serve as a reverse-proxy for our Docker internal registry. In our tutorial, NGiNX will be responsible for our domain name routing and also for handling the TLS layer required by Docker for pushing and pulling images. Without having a secured (SSL) connection, basic docker commands like docker push or docker pull will fail during runtime.

 

Update nginx.conf configuration

Our first step with NGiNX configuration begins by changing the default value for client_max_body_size, this particular NGiNX flag sets the maximum allowed size of the client request body, which by default it is 1m. As we will be using NGiNX to push (upload) and pull (download) Docker images the default value is not what we should be using, all Docker images will be at least 50m in size. So let’s open this file and increase the value to something more reasonable for our Docker registry setup:

vi /etc/nginx/nginx.conf

By default client_max_body_size it’s not included in the main nginx.conf file as a standard parameter, but we should add it as shown in the example below having a value of 2000m:

http {

        client_max_body_size 2000M;
        ...
}

We can tweak this value anytime if we will ever encounter an error like 413 while pushing images to our registry. We should now save and close nginx.conf configuration file as we will be moving to our next step.

 

Create logs and include.ddirectories

Within our NGiNX configuration steps for a private Docker registry we have to create three directories, first one will be called docker-registry and will be created underneath /var/log/nginx/ and it will serve for registry logs, more precisely for access logs and error logs. The second one will be named include.d and will located under /etc/nginx/ directory, this second directory will contain two configuration files responsible for the SSL / TLS configuration layer. Third directory is called ssl, being located under /etc/nginx/ and it will contain our SSL certificate component files (.crt, .key etc.). Let’s create these two directories as shown here using just a single command line:

mkdir -p /var/log/nginx/docker-registry && mkdir -p /etc/nginx/{include.d,ssl}

 

Created docker-registry.conf file

In this particular step we need to define our Docker registry NGiNX configuration file, we may treat this step as the vHost configuration step.

vi /etc/nginx/conf.d/docker-registry.conf

Our docker-registry.conf file will contain the next lines:

#
# Reference: Tufora.com
#

upstream docker-registry {

	server 			localhost:5000;

}

server {

	listen 			[::]:80 default_server;
	server_name             dockerrepo.dummy-domain.com;

    	return 			302 https://$server_name$request_uri;

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

}

server {

    	listen 443 ssl http2 default_server;
    	listen [::]:443 ssl http2 default_server;

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

	include include.d/*.conf;

	location / {

	    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;
	    proxy_read_timeout	900;

	}

}

As you can see, server_name parameter contains a dockerrepo.dummy-domain.com; value, meaning that the domain name that we’ll be calling within our docker push or docker pull commands will be dockerrepo.dummy-domain.com. Please make sure that a similar domain name exists and it does have a DNS (Type A) entry, pointing to the Docker registry server IP address (subdomain.domain.tld > DNS A Entry > Docker Registry IP Address).

 

Create ssl-certs.conf file

Now, we have to create a filename called ssl-certs.conf, this particular file will only hold details about our SSL component files location, so let’s create this file:

vi /etc/nginx/include.d/ssl-certs.conf

The content of ssl-certs.conf will look similar to this:

#
# Reference: Tufora.com
#

ssl_certificate 	/etc/nginx/ssl/wildcard.dummy-domain.pem;
ssl_certificate_key 	/etc/nginx/ssl/wildcard.dummy-domain.key;

Amend the file names according to your specific use case and naming convention, and finally save and close the file.

If you don’t have a SSL certificate for this specific domain name you can always generate and use a free one using Let’s Encrypt, you can issue a free SSL by following our tutorial called Secure NGiNX with Let’s Encrypt SSL on CentOS 7.

 

Create ssl-params.conf file

Here, we will be defining all necessary SSL / TLS parameters needed in order to secure the connection between NGiNX (Docker Registry) and Docker Clients (other internal Docker servers) that will use docker push / pull, so let’s edit this file like shown here:

vi /etc/nginx/include.d/ssl-params.conf

And use the next parameter lines as its content:

#
# Reference: Tufora.com
#

ssl_protocols 			TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers 	on;
ssl_ciphers 			"EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
ssl_ecdh_curve 			secp384r1;
ssl_session_cache 		shared:SSL:10m;
ssl_session_tickets 		off;
ssl_stapling 			off;
ssl_stapling_verify 		off;

resolver 			8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 		5s;

add_header 			Strict-Transport-Security "max-age=63072000; includeSubdomains";
add_header 			X-Frame-Options DENY;
add_header 			X-Content-Type-Options nosniff;

ssl_dhparam 			/etc/ssl/certs/dhparam.pem;

We can safely save and close ssl-params.conf, we are finally done with all NGiNX configuration files needed for our private registry, let’s move to the next NGiNX step where we have to test the configuration that we have just built.

 

Check NGiNX configuration and reload the service

One step close to the end of our tutorial about how to set up a Private Docker Registry, here we need to test if all above files and their parameters will pass NGiNX tests, so let’s run this on our terminal window:

nginx -t

A successful output will look just like this:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

If any errors are showing up please go back and fix them before moving to the next step, repeat the NGiNX test step until the above result shows up.

Knowing that our NGiNX test has been passed successfully we can now safely reload NGiNX:

systemctl nginx reload

At this stage, all our custom configuration files and their parameters were read, loaded and used by NGiNX.

Test Docker Registry URL

It is time to test our Docker registry solution, we can perform a very quick test by simply using curl like this:

curl -H  "Host: dockerrepo.dummy-domain.com" -IL localhost

A successful output, confirming that our Private Docker Registry is now up and running will look just like this:

HTTP/1.1 302 Moved Temporarily
Server: nginx/1.16.1
Date: Sun, 22 Dec 2019 20:39:20 GMT
Content-Type: text/html
Content-Length: 145
Connection: keep-alive
Location: https://dockerrepo.dummy-domain.com/

HTTP/1.1 200 OK
Server: nginx/1.16.1
Date: Sun, 22 Dec 2019 20:39:20 GMT
Connection: keep-alive
Cache-Control: no-cache
Strict-Transport-Security: max-age=63072000; includeSubdomains
X-Frame-Options: DENY
X-Content-Type-Options: nosniff

We can clearly see that NGiNX works as expected, a 302 redirect to HTTPS is in place and enforced, and also a successful 200 response code is received.

On the same note, we should be able to see the connection initiated to our private registry by checking the logs using cat command like shown here:

cat /var/log/nginx/docker-registry/access.log

A successful output will looks something like this:

127.0.0.1 - - [04/Oct/2019:12:28:19 +0100] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.29.0" "-"

This technically means that we have successfully managed to build our own private docker registry, this internal registry it is now up and running and we can make use of it as of now.

Final notes

From a security prospective we should make sure that our Private Docker Registry it is not publicly available as we have no security or authentication layer in place. Safest option is to use only internal IP addresses for all DNS entries that are pointing to our local registry, never use public IP addresses (Inbound / Ingress NAT). If we have no choice than to use Public IP addresses then we should make sure that all connections to both ports, 80 and 443, are dropped or at least locked via source IP.

In terms of scalability and availability this solution can be easily extended by adding multiple registry nodes into the mix.

Video

No video posted for this page.

Screenshots

No screenshots posted for this page.

Source code

No code posted for this page.

About this page

Article
Setup a Private Docker Registry
Author
Category
Published
20/12/2019
Updated
31/12/2019
Tags

Share this page

If you found this page useful please share it with your friends or colleagues.