• Get In Touch
June 11, 2016

Setup a Private Docker Registry Server on Ubuntu 14.04

Want your very own server? Get our 1GB memory, Xeon V4, 25GB SSD VPS for £10.00 / month.
Get a Cloud Server

Docker is a platform to create, manage and distribute application containers across multiple machines. Docker Inc provides a service to host open source containers to be downloaded or pulled like a git repository known as the Docker Registry.

The registry is a stateless, highly scalable server side application that stores and lets you distribute Docker Images. The Registry is open-source, under the permissive Apache license.

Docker has a public registry called Docker Hub to store Docker Images. Docker allows you to upload your images to Docker Hub for free, but anything you upload is public. So it is recommend to create your own private Docker registry.

You should use the registry if you want to:

  • Tightly control where your images are being stored
  • Fully own your images distribution pipeline
  • Integrate image storage and distribution tightly into your in-house development workflow

In this tutorial, you will learn how to set up and secure your own private Docker registry on Ubuntu-14.04.

Requirements

  • A server running Ubuntu-14.04 with Docker installed on your system.
  • A non-root user account with sudo privilege set up on your server.

Installing Docker Compose

Distributed applications consist of many small applications that work together. Docker transforms these applications into individual containers that are linked together. Instead of having to build, run and manage each individual container, Docker Compose allows you to define your multi-container application with all of its dependencies in a single file, then spin your application up in a single command. Your application’s structure and configuration are held in a single place, which makes spinning up applications simple and repeatable everywhere.
You will need to install python-pip as prerequisite to install Docker compose.

sudo apt-get -y install python-pip

Then, install Docker Compose by running the following command:

sudo pip install docker-compose

Next, you will need to install the apache2-utils package which contains the htpasswd utility that can easily generate password hashes Nginx can understand:

sudo apt-get -y install apache2-utils

Installing and Configuring the Docker Registry

Docker Compose allows you to write one .yml configuration file for the configuration for each container. Then you can run the docker-compose command to all the components that make up an application.

The Docker registry is an application with multiple components. Here, we will use Docker Compose to manage our configuration.

Let’s create directory structure where your registry will be storing its data.

sudo mkdir -p /Docker-Regisry/Data
cd /Docker-Regisry/Data

Next, create docker-compose.yml file:

sudo nano docker-compose.yml

Add the following contents:

    registry:
      image: registry:2
      ports:
        - 127.0.0.1:5000:5000
      environment:
        REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /Data
      volumes:
        - ./Data:/Data 

Save and close the file.

In the above file, the environment section sets an environment variable in the Docker registry container with the path /Data.

The Docker registry app saves its data to the /Data folder. The volumes: - ./Data:/Data section is telling Docker that the /Data directory in that container maps out to /Data on our host machine. Finally the Docker registry’s data stored in /Docker-Registry/Data on our local machine.

Now, let’s start up Docker Registry by running the following command:

cd /Docker-Registry
sudo docker-compose up

You should see the following output:

    Pulling registry (registry:2)...
    2: Pulling from library/registry
    8b87079b7a06: Pull complete
    a3ed95caeb02: Pull complete
    ab57f16e019e: Pull complete
    1ca2ed86f0e6: Pull complete
    c2021377c865: Pull complete
    Digest: sha256:bf9b4a7b53a2f54c7b4d839103ca5be05b6a770ee0ba9c43e9ef23d602414f44
    Status: Downloaded newer image for registry:2
    Creating dockerregistry_registry_1
    Attaching to dockerregistry_registry_1
    registry_1  | time="2016-05-31T16:34:08Z" level=warning msg="No HTTP secret provided - generated random secret. This may cause problems with uploads if multiple registries are behind a load-balancer. To provide a shared secret, fill in http.secret in the configuration file or set the REGISTRY_HTTP_SECRET environment variable." go.version=go1.6.2 instance.id=d9792f2f-259c-4cbf-bbf9-d403f27fd625 version=v2.4.1 
    registry_1  | time="2016-05-31T16:34:08Z" level=info msg="Starting upload purge in 40m0s" go.version=go1.6.2 instance.id=d9792f2f-259c-4cbf-bbf9-d403f27fd625 version=v2.4.1 
    registry_1  | time="2016-05-31T16:34:08Z" level=info msg="redis not configured" go.version=go1.6.2 instance.id=d9792f2f-259c-4cbf-bbf9-d403f27fd625 version=v2.4.1 
    registry_1  | time="2016-05-31T16:34:09Z" level=info msg="using inmemory blob descriptor cache" go.version=go1.6.2 instance.id=d9792f2f-259c-4cbf-bbf9-d403f27fd625 version=v2.4.1 
    registry_1  | time="2016-05-31T16:34:09Z" level=info msg="listening on [::]:5000" go.version=go1.6.2 instance.id=d9792f2f-259c-4cbf-bbf9-d403f27fd625 version=v2.4.1 

Now your Docker registry up and running and listening on port 5000. At this point the registry isn’t useful, it doesn’t come with any built-in authentication mechanism, so it is insecure.

Press CTRL-C to shut down your Docker registry container.

Setting Up An Nginx Container

To overcome these security issues. You will need to set up a copy of Nginx inside another Docker container and link it up to our Docker registry container.

Create a directory to store our Nginx configuration:

sudo mkdir /Docker-Registry/nginx

Now, open docker-compose.yml file:

sudo nano /Docker-Registry/docker-compose.yml

Add the following content into the top of the file:

    nginx:
      image: "nginx:1.9"
      ports:
        - 5043:443
      links:
        - registry:registry
      volumes:
        - ./nginx/:/etc/nginx/conf.d:ro

Save and close the file. The above file will create a new Docker container based on the official Nginx image. The link section automatically sets up a “link” from one Docker container to the another. When the Nginx container starts up, it will be able to reach the registry container at the hostname registry.

The docker-compose up command will now start two containers at the same time: one for the Docker registry and one for Nginx.

Before starting docker-compose, you will need to create a new Nginx configuration file.

sudo nano /Docker-Registry/nginx/registry.conf

Add the following content:

    upstream docker-registry {
      server registry:5000;
    }

    server {
      listen 443;
      server_name myregistrydomain.com;

      # SSL
      # ssl on;
      # ssl_certificate /etc/nginx/conf.d/domain.crt;
      # ssl_certificate_key /etc/nginx/conf.d/domain.key;

      # disable any limits to avoid HTTP 413 for large image uploads
      client_max_body_size 0;

      # required to avoid HTTP 411: see Issue #1486 (https://github.com/docker/docker/issues/1486)
      chunked_transfer_encoding on;

      location /v2/ {
        # Do not allow connections from docker 1.5 and earlier
        # docker pre-1.6.0 did not properly set the user agent on ping, catch "Go *" user agents
        if ($http_user_agent ~ "^(docker/1.(3|4|5(?!.[0-9]-dev))|Go ).*$" ) {
          return 404;
        }

        # To add basic authentication to v2 use auth_basic setting plus add_header
        # auth_basic "registry.localhost";
        # auth_basic_user_file /etc/nginx/conf.d/registry.password;
        # add_header 'Docker-Distribution-Api-Version' 'registry/2.0' always;

        proxy_pass                          http://docker-registry;
        proxy_set_header  Host              $http_host;   # required for docker client's sake
        proxy_set_header  X-Real-IP         $remote_addr; # pass on real client's IP
        proxy_set_header  X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header  X-Forwarded-Proto $scheme;
        proxy_read_timeout                  900;
      }
    }

Save and close the file.

Now you can start up the two Docker containers by running the following command:

cd /Docker-Registry
sudo docker-compose up

You should see the following output:

    Pulling nginx (nginx:1.9)...
    1.9: Pulling from library/nginx
    51f5c6a04d83: Pull complete
    a3ed95caeb02: Pull complete
    640c8f3d0eb2: Pull complete
    a4335300aa89: Pull complete
    Digest: sha256:54313b5c376892d55205f13d620bc3dcccc8e70e596d083953f95e94f071f6db
    Status: Downloaded newer image for nginx:1.9
    Starting dockerregistry_registry_1
    Creating dockerregistry_nginx_1
    Attaching to dockerregistry_registry_1, dockerregistry_nginx_1
    registry_1  | time="2016-05-31T16:49:04Z" level=warning msg="No HTTP secret provided - generated random secret. This may cause problems with uploads if multiple registries are behind a load-balancer. To provide a shared secret, fill in http.secret in the configuration file or set the REGISTRY_HTTP_SECRET environment variable." go.version=go1.6.2 instance.id=b12d5a57-25a5-41a0-abfb-4e290ea8c767 version=v2.4.1 
    registry_1  | time="2016-05-31T16:49:04Z" level=info msg="redis not configured" go.version=go1.6.2 instance.id=b12d5a57-25a5-41a0-abfb-4e290ea8c767 version=v2.4.1 
    registry_1  | time="2016-05-31T16:49:04Z" level=info msg="Starting upload purge in 15m0s" go.version=go1.6.2 instance.id=b12d5a57-25a5-41a0-abfb-4e290ea8c767 version=v2.4.1 
    registry_1  | time="2016-05-31T16:49:04Z" level=info msg="using inmemory blob descriptor cache" go.version=go1.6.2 instance.id=b12d5a57-25a5-41a0-abfb-4e290ea8c767 version=v2.4.1 
    registry_1  | time="2016-05-31T16:49:04Z" level=info msg="listening on [::]:5000" go.version=go1.6.2 instance.id=b12d5a57-25a5-41a0-abfb-4e290ea8c767 version=v2.4.1 

After starting up both containers, you can test it by using curl to make an HTTP request to our Docker registry and make another request to our Nginx port. If everything is set up correctly the output will be the same in both cases.

Let’s, make an HTTP request directly to the Docker registry:

curl http://localhost:5000/v2/

Output:

    {}

Now send an HTTP request to the Nginx port:

curl http://localhost:5043/v2/

Output:

    {}

If all is working well you’ll see some output in your docker-compose terminal that looks like the following:

    registry_1  | time="2016-05-31T16:51:03Z" level=info msg="response completed" go.version=go1.6.2 http.request.host="localhost:5000" http.request.id=d9318b45-b721-4053-b279-621475b78173 http.request.method=GET http.request.remoteaddr="172.17.0.2:39569" http.request.uri="/v2/" http.request.useragent="curl/7.35.0" http.response.contenttype="application/json; charset=utf-8" http.response.duration=2.936977ms http.response.status=200 http.response.written=2 instance.id=b12d5a57-25a5-41a0-abfb-4e290ea8c767 version=v2.4.1 
    registry_1  | 172.17.0.2 - - [31/May/2016:16:51:03 +0000] "GET /v2/ HTTP/1.1" 200 2 "" "curl/7.35.0"
    registry_1  | time="2016-05-31T16:51:24Z" level=info msg="response completed" go.version=go1.6.2 http.request.host="localhost:5043" http.request.id=e6ac055b-38f4-49d7-9ebc-45c8ca1062b0 http.request.method=GET http.request.remoteaddr=172.17.0.2 http.request.uri="/v2/" http.request.useragent="curl/7.35.0" http.response.contenttype="application/json; charset=utf-8" http.response.duration=3.792746ms http.response.status=200 http.response.written=2 instance.id=b12d5a57-25a5-41a0-abfb-4e290ea8c767 version=v2.4.1 
    nginx_1     | 172.17.0.2 - - [31/May/2016:16:51:24 +0000] "GET /v2/ HTTP/1.1" 200 2 "-" "curl/7.35.0" "-"
    registry_1  | 172.17.0.4 - - [31/May/2016:16:51:24 +0000] "GET /v2/ HTTP/1.0" 200 2 "" "curl/7.35.0"

Setup Authentication

Next, you will need to setup HTTP authentication so you can control Docker registry with user authentication.

You can create authentication file using htpasswd utility.

Create first a user with the USERNAME as you wish:

cd /Docker-Registry/nginx
sudo htpasswd -c registry.password USERNAME

Note: If you want to create more users then run above command without -c option.

Next, you will need to tell Nginx to use that authentication file:

You can do this by editing registry.conf file:

sudo nano /Docker-Registry/nginx/registry.conf

Uncomment the following lines:

    # To add basic authentication to v2 use auth_basic setting plus add_header
    auth_basic "registry.localhost";
    auth_basic_user_file /etc/nginx/conf.d/registry.password;
    add_header 'Docker-Distribution-Api-Version' 'registry/2.0' always;

Save and close the file and test whether authentication is working:

cd /Docker-Registry

sudo docker-compose up

Now, run curl command:

curl http://localhost:5043/v2/

You should see authorisation message:


<title>401 Authorization Required</title> <h1>401 Authorization Required</h1> <hr>nginx/1.9.7

Next, add username and password with curl command:

curl http://USERNAME:PASSWORD@localhost:5043/v2/

Output:

    {}

It means that everything is working properly.

Setup SSL

Now your Docker Registry is up and running with basix HTTP authentication. But the connections are unencrypted and the setup is not secure, so you will need to secure it with SSL.

You can do this by editing registry.conf file:

sudo nano /Docker-Registry/nginx/registry.conf

Uncomment the following line:

    ssl on;
    ssl_certificate /etc/nginx/conf.d/domain.crt;
    ssl_certificate_key /etc/nginx/conf.d/domain.key;

Save and close the file. Nginx is now configured to use SSL.

Next, you will need to create your own SSL certificate.

To begin, change the directory to /Docker-Registry/nginx:

cd /Docker-Registry/nginx

Generate a root key using the following command:

openssl genrsa -out CA.key 2048

Next, generate a root certificate:

openssl req -x509 -new -nodes -key CA.key -days 10000 -out CA.crt

Then generate a key for your server:

openssl genrsa -out domain.key 2048

Now, you need to make a certificate signing request:

openssl req -new -key domain.key -out CA.csr

Fill in all information as you wish:

    Country Name (2 letter code):IN
    State or Province Name (full name):GUJARAT
    Locality Name (eg, city):AHMEDABAD
    Organization Name (eg, company):Hostpresto
    Organizational Unit Name (eg, section):IT
    Common Name (e.g. server FQDN or YOUR name):myregistrydomain.com
    Email Address []:hitjethva@gmail.com

    Please enter the following 'extra' attributes
    to be sent with your certificate request
    A challenge password []:
    An optional company name []:Hostpresto

Next, you need to sign the certificate request:

openssl x509 -req -in CA.csr -CA CA.crt -CAkey CA.key -CAcreateserial -out domain.crt -days 10000

The certificates you have just created are not verified by any known certificate authority. So you need to copy CA.crt on the host machine so that we can use Docker from the Docker registry server itself:

sudo cp CA.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates

    Updating certificates in /etc/ssl/certs... 1 added, 0 removed; done.
    Running hooks in /etc/ca-certificates/update.d....done.

Now, restart Docker service to picks up the changes :

sudo service docker restart

Testing SSL

Now, start Docker container with docker-compose up command:

cd /Docker-Registry
sudo docker-compose up

Now, run curl command to verify that your SSL setup is working properly.

curl https://USERNAME:PASSWORD@myregistrydomain.com:5043/v2/

Note: If you are using a self-signed certificate, you will see the following error from curl:

    ```
    curl: (60) SSL certificate problem: self signed certificate
    ```

To ignore this error run curl command with -k option:

curl -k https://USERNAME:PASSWORD@myregistrydomain.com:5043/v2/

If everything is working fine, you should see the following output:

    {}

Create an Upstart Script for Docker Registry

If everything is working fine, then create an Upstart script for Docker Registry that start automatically whenever the system boots up.

You can create an Upstart script by creating docker-registry.conf file inside /etc/init directory:

sudo nano /etc/init/docker-registry.conf

Add the following content:

    description "Docker Registry"

    start on runlevel [2345]
    stop on runlevel [016]

    respawn
    respawn limit 10 5

    cd /Docker-registry

    exec /usr/local/bin/docker-compose up

Save and close the file.

You can test your new Upstart script by running the following command:

sudo service docker-registry start

You should see something like this:

    docker-registry start/running, process 14209

You can verify the server by running the following command:

sudo docker ps

The output should look similar to the following:

    CONTAINER ID        IMAGE               COMMAND                CREATED             STATUS              PORTS                          NAMES
    c5b6def0a4d1        nginx:1.9           "nginx -g 'daemon of   1 minutes ago       Up 1 minutes        80/tcp, 0.0.0.0:443->443/tcp   dockerregistry_nginx_1
    25662552st14        registry:2          "registry cmd/regist   1 minutes ago       Up 1 minutes        127.0.0.1:5000->5000/tcp       dockerregistry_registry_1

Upstart will log the output of the docker-compose command to /var/log/upstart/docker-registry.log.

Accessing Your Docker Registry From a Client Machine

You can also access your Docker registry from another machine. To do this, you will need to add the SSL certificate you created earlier to the client machine. The file you need is located at /Docker-Registry/nginx/CA.crt.

You can copy CA.crt file from registry server to client machine by running the following command on the server machine:

sudo scp /Docker-Registry/nginx/CA.crt root@client-machine:/usr/local/share/ca-certificates/

Next, on client machine update the certificate by running the following command:

sudo update-ca-certificates

You should see the following output:

    Updating certificates in /etc/ssl/certs... 1 added, 0 removed; done.
    Running hooks in /etc/ca-certificates/update.d....done.

Now, restart Docker service to picks up the changes :

sudo service docker restart

You should now be able to log in to your Docker registry from the client machine:

sudo docker login https://YOUR-DOMAIN:5043

Enter the username and password:

    Username: USERNAME
    Password: PASSWORD
    Email: 
    Account created. Please see the documentation of the registry http://localhost:5000/v1/ for instructions how to activate it.

You should see the following message:

    Output of docker login
    Login Succeeded

Finally your Docker registry is up and running.

Publish An Image to Your Private Docker Registry

You are now able to publish an image to your private Docker registry. But first you will need to create your own image.

On client machine, download Ubuntu image from Docker Hub:

sudo docker run -t -i ubuntu /bin/bash

You should see the following output:

    Pulling repository ubuntu
    118aadd1f859: Pulling dependent layers
    41402770caf2: Pulling fs layer
    a5051dd98acd: Download complete
    docker run -t -i ubuntu /bin/basha3ed95caeb02: Download complete
    . . .

    Status: Downloaded newer image for ubuntu

After it finishes downloading you’ll be inside a Docker prompt. Now, create some files inside filesystem:

touch /Docker /Docker-reg /Testing

Now, exit the Docker container:

exit

Commit the changes to new image:

docker commit $(docker ps -lq) test-image

The above comm command creates a new image called test-image based on the image already running plus any changes you have made.

Next, push this new test-image to your private Docker drgistry.

To do this, first login to your private Docker registry.

sudo docker login https://YOUR-DOMAIN:5043

Enter the username and password you set up earlier:

    Username: USERNAME
    Password: PASSWORD
    Email: 
    Account created. Please see the documentation of the registry http://localhost:5000/v1/ for instructions how to activate it.

First, you need to tag an image with the private registry’s location in order to push to it.

sudo docker tag test-image [YOUR-DOMAIN]/test-image

Now, run the following command to push test-image to your private Docker registry:

sudo docker push [YOUR-DOMAIN]/test-image

This will take some time to upload to the registry server.

Pull an Image from Your Private Docker Registry

If everything is working fine, then let’s go to other client machine and pull the test-image we have just pushed.
Before starting, make sure docker is installed on client machine:

Now, login to Docker registry server:

sudo docker login https://YOUR-DOMAIN:5043

Enter the username and password you set up earlier:

    Username: USERNAME
    Password: PASSWORD
    Email:
    Account created. Please see the documentation of the registry http://localhost:5000/v1/ for instructions how to activate it.

After login, pull the image using the following command:

sudo docker pull [YOUR-DOMAIN]/test-image

Docker will take some time for downloading and return you to the prompt.

Next, run downloaded test-image using the following command:

sudo docker run -t -i [YOUR-DOMAIN]/test-image /bin/bash

On docker shell, list your files using the following command:

ls

You should see the Docker, Docker-reg and Testing file we created earlier for this image:

    Docker Docker-reg Testing bin  boot  dev  etc  home  lib  lib64  media   mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var

Conclusion

Congratulations! You can now easily push and pull images from your own private Docker registry server.

Want your very own server? Get our 1GB memory, Xeon V4, 25GB SSD VPS for £10.00 / month.
Get a Cloud Server

Share this Article!

Related Posts

Node.js Authentication – A Complete Guide with Passport and JWT

Node.js Authentication – A Complete Guide with Passport and JWT

Truth be told, it’s difficult for a web application that doesn’t have some kind of identification, even if you don’t see it as a security measure in and of itself. The Internet is a kind of lawless land, and even on free services like Google’s, authentication ensures that abuses will be avoided or at least […]

Node.js and MongoDB: How to Connect MongoDB With Node

Node.js and MongoDB: How to Connect MongoDB With Node

MongoDB is a document-oriented NoSQL database, which was born in 2007 in California as a service to be used within a larger project, but which soon became an independent and open-source product. It stores documents in JSON, a format based on JavaScript and simpler than XML, but still with good expressiveness. It is the dominant […]

Using MySQL with Node.js: A Complete Tutorial

Using MySQL with Node.js: A Complete Tutorial

Although data persistence is almost always a fundamental element of applications, Node.js has no native integration with databases. Everything is delegated to third-party libraries to be included manually, in addition to the standard APIs. Although MongoDB and other non-relational databases are the most common choice with Node because if you need to scale an application, […]

Node.Js Vs Django: Which Is the Best for Your Project

Node.Js Vs Django: Which Is the Best for Your Project

Django and NodeJs are two powerful technologies for web development, both have great functionality, versatile applications, and a great user interface. Both are open source and can be used for free. But which one fits your project best? NodeJs is based on JavaScript, while Django is written in Python. These are two equally popular technologies […]

Nodejs Vs PHP:  Which Works Best?

Nodejs Vs PHP: Which Works Best?

Before getting into the “battle” between Node.js and PHP we need to understand why the issue is still ongoing. It all started with the increased demand for smartphone applications, their success forcing developers to adapt to new back-end technologies that could handle a multitude of simultaneous requests. JavaScript has always been identified as a client-side […]