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.