Tutorial

How To Set Up a Private Docker Registry on Ubuntu 14.04

Published on October 15, 2014
How To Set Up a Private Docker Registry on Ubuntu 14.04
Not using Ubuntu 14.04?Choose a different version or distribution.
Ubuntu 14.04

Introduction

Docker is a great tool for deploying your servers. Docker even has a public registry called Docker Hub to store Docker images. While Docker lets you upload your Docker creations to their Docker Hub for free, anything you upload is also public. This might not be the best option for your project.

This guide will show you how to set up and secure your own private Docker registry. By the end of this tutorial you will be able to push a custom Docker image to your private registry and pull the image securely from a different host.

This tutorial doesn’t cover containerizing your own application but only how to create the registry where you can store your deployments. If you want to learn how to get started with Docker itself (as opposed to the registry), you may want to read the How To Install and Use Docker: Getting Started tutorial.

This tutorial has been tested with both the registry server and registry client running Ubuntu 14.04, but it may work with other Debian-based distributions. It also covers version 2.0 of the Docker Registry.

Docker Concepts

If you haven’t used Docker before then it’s worth taking a minute to go through a few of Docker’s key concepts. If you’re already using Docker and just want to know how to get started running your own registry, then please skip ahead to the next section.

For a refresher on how to use Docker, take a look at the excellent Docker Cheat Sheet.

Docker at its core is a way to separate an application and the dependencies needed to run it from the operating system itself. To make this possible Docker uses containers and images. A Docker image is basically a template for a filesystem. When you run a Docker image, an instance of this filesystem is made live and runs on your system inside a Docker container. By default this container can’t touch the original image itself or the filesystem of the host where Docker is running. It’s a self-contained environment.

Whatever changes you make in the container are preserved in that container itself and don’t affect the original image. If you decide you want to keep those changes, then you can “commit” a container to a Docker image (via the docker commit command). This means you can then spawn new containers that start with the contents of your old container, without affecting the original container (or image). If you’re familiar with git, then the workflow should seem quite similar: you can create new branches (images in Docker parlance) from any container. Running an image is a bit like doing a git checkout.

To continue the analogy, running a private Docker registry is like running a private Git repository for your Docker images.

Prerequisites

To complete this tutorial, you will need the following:

Step 1 — Installing Package for Added Security

To set up security for the Docker Registry it’s best to use Docker Compose. This way we can easily run the Docker Registry in one container and let Nginx handle security and communication with the outside world in another. You should already have it installed from the Prerequisites section.

Since we’ll be using Nginx to handle our security, we’ll also need a place to store the list of username and password combinations that we want to access our registry. We’ll install the apache2-utils package which contains the htpasswd utility that can easily generate password hashes Nginx can understand:

  1. sudo apt-get -y install apache2-utils

Step 2 — Installing and Configuring the Docker Registry

The Docker command line tool works great for starting and managing a Docker container or two, but most apps running inside Docker containers don’t exist in isolation. To fully deploy most apps you need a few components running in parallel. For example, most web applications are made up of a web server that serves up the app’s code, an interpreted scripting language such as PHP or Ruby (with Rails), and a database server like MySQL.

Docker Compose allows you to write one .yml configuration file for the configuration for each container as well as information about how the containers communicate with each other. You then use the docker-compose command line tool to issue commands to all the components that make up an application.

Since the Docker registry itself is an application with multiple components, we’ll use Docker Compose to manage our configuration.

To start a basic registry the only configuration needed is to define the location where your registry will be storing its data. Let’s set up a basic Docker Compose YAML file to bring up a basic instance of the registry.

First create a folder where our files for this tutorial will live and some of the subfolders we’ll need:

  1. mkdir ~/docker-registry && cd $_
  2. mkdir data

Using your favorite text editor, create a docker-compose.yml file:

  1. nano docker-compose.yml

Add the following contents to the file:

docker-compose.yml
registry:
  image: registry:2
  ports:
    - 127.0.0.1:5000:5000
  environment:
    REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /data
  volumes:
    - ./data:/data

The interesting bit here is at the end. The environment section sets an environment variable in the Docker registry container with the path /data. The Docker registry app knows to check this environment variable when it starts up and to start saving its data to the /data folder as a result.

Only in this case, the volumes: - ./data:/data bit is telling Docker that the /data directory in that container should actually map out to /data on our host machine. So the end result is that the Docker registry’s data all gets stored in ~/docker-registry/data on our local machine.

Let’s go ahead and start it up to make sure everything is in order:

  1. cd ~/docker-registry
  2. docker-compose up

You’ll see a bunch of download bars come move across your screen (this is Docker downloading the actual Docker registry image from Docker’s own Docker Registry). If everything went well in a minute or two, you should see output that looks like this (versions might vary):

Output of docker-compose up
registry_1 | time="2015-10-18T23:45:58Z" 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." instance.id=44c828de-c27a-401e-bb2e-38b17e6a4b7b version=v2.1.1 registry_1 | time="2015-10-18T23:45:58Z" level=info msg="redis not configured" instance.id=44c828de-c27a-401e-bb2e-38b17e6a4b7b version=v2.1.1 registry_1 | time="2015-10-18T23:45:58Z" level=info msg="using inmemory blob descriptor cache" instance.id=44c828de-c27a-401e-bb2e-38b17e6a4b7b version=v2.1.1 registry_1 | time="2015-10-18T23:45:58Z" level=info msg="listening on [::]:5000" instance.id=44c828de-c27a-401e-bb2e-38b17e6a4b7b version=v2.1.1 registry_1 | time="2015-10-18T23:45:58Z" level=info msg="Starting upload purge in 1m0s" instance.id=44c828de-c27a-401e-bb2e-38b17e6a4b7b version=v2.1.1

Don’t worry about the No HTTP secret provided message. It’s normal.

Great! At this point you’ve already got a full Docker registry up and running and listening on port 5000 (this was set by the ports: bit in the docker-compose.yml file). At this point the registry isn’t that useful yet — it won’t start unless you bring up the registry manually. Also, Docker registry doesn’t come with any built-in authentication mechanism, so it’s insecure and completely open to the public right now.

Docker Compose will by default stay waiting for your input forever, so go ahead and hit CTRL-C to shut down your Docker registry container.

Step 3 — Setting Up an Nginx Container

Let’s get to work on fixing these security issues. The first step is to set up a copy of Nginx inside another Docker container and link it up to our Docker registry container.

Let’s start by creating a directory to store our Nginx configuration:

  1. mkdir ~/docker-registry/nginx

Now, re-open your docker-compose.yml file in the ~/docker-registry directory:

  1. nano docker-compose.yml

Paste the following into the top of the file:

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

This will create a new Docker container based on the official Nginx image. The interesting bit here is the links section. It automagically set 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 no matter what the actual IP address the registry container ends up having. (Behind the scenes Docker is actually inserting an entry into the /etc/hosts file in the nginx container to tell it the IP of the registry container).

The volumes: section is similar to what we did for the registry container. In this case it gives us a way to store the config files we’ll use for Nginx on our host machine instead of inside the Docker container. The :ro at the end just tells Docker that the Nginx container should only have read-only access to the host filesystem.

Your full docker-compose.yml file should now look like this:

docker-compose.yml
nginx:
  image: "nginx:1.9"
  ports:
    - 5043:443
  links:
    - registry:registry
  volumes:
    - ./nginx/:/etc/nginx/conf.d
registry:
  image: registry:2
  ports:
    - 127.0.0.1:5000:5000
  environment:
    REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /data
  volumes:
    - ./data:/data

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

We need to configure Nginx before this will work though, so let’s create a new Nginx configuration file.

Create a registry.conf file:

  1. nano ~/docker-registry/nginx/registry.conf

Copy the following into the file:

~/docker-registry/nginx/registry.conf
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 exit the file.

Now you can install Nginx and start up the two Docker containers all with one command:

  1. docker-compose up

Nginx doesn’t print any output on startup, but if all went well you’re now running a copy of Nginx that is set up to proxy to your registry container. To test it, let’s use curl to make an HTTP request to our Docker registry directly, and then make another request to our Nginx port. If everything is set up correctly the output will be the same in both cases (as of this writing Docker returns an empty json object “{}”) since Nginx will proxy the request through to the Docker registry.

First, make an HTTP request directly to the Docker registry:

  1. curl http://localhost:5000/v2/

As of this writing Docker returns an empty json object, so you should see:

Output
{}

Now send an HTTP request to the Nginx port:

  1. curl http://localhost:5043/v2/

You should see the same output:

Output
{}

If things are working correctly you’ll see some output in your docker-compose terminal that looks like the below as well:

Output of docker-compose
registry_1 | time="2015-08-11T10:24:53.746529894Z" level=debug msg="authorizing request" environment=development http.request.host="localhost:5043" http.request.id=55c3e2a6-4f34-4b0b-bc57-11c814b4f4d3 http.request.method=GET http.request.remoteaddr=172.17.42.1 http.request.uri="/v2/" http.request.useragent="curl/7.35.0" instance.id=55634dfc-c9e0-4ec9-9872-6f4930c17759 service=registry version=v2.0.1 registry_1 | time="2015-08-11T10:24:53.747650205Z" level=info msg="response completed" environment=development http.request.host="localhost:5043" http.request.id=55c3e2a6-4f34-4b0b-bc57-11c814b4f4d3 http.request.method=GET http.request.remoteaddr=172.17.42.1 http.request.uri="/v2/" http.request.useragent="curl/7.35.0" http.response.contenttype="application/json; charset=utf-8" http.response.duration=8.143193ms http.response.status=200 http.response.written=2 instance.id=55634dfc-c9e0-4ec9-9872-6f4930c17759 service=registry version=v2.0.1 registry_1 | 172.17.0.21 - - [11/Aug/2015:10:24:53 +0000] "GET /v2/ HTTP/1.0" 200 2 "" "curl/7.35.0" nginx_1 | 172.17.42.1 - - [11/Aug/2015:10:24:53 +0000] "GET /v2/ HTTP/1.1" 200 2 "-" "curl/7.35.0" "-"

If you see lines with the registry_ prefix (the number after the _ may be different on your machine) then all is well, and Nginx has successfully proxied our HTTP request to the Docker registry.

Go ahead and hit CTRL-C again in your docker-compose terminal to shut down your Docker containers.

Step 4 — Setting Up Authentication

Now that Nginx is proxying requests properly let’s set it up with HTTP authentication so that we can control who has access to our Docker registry. To do that we’ll create an authentication file in Apache format (Nginx can read it too) via the htpasswd utility we installed earlier and add users to it.

Create the first user as follows, replacing USERNAME with the username you want to use:

  1. cd ~/docker-registry/nginx
  2. htpasswd -c registry.password USERNAME

Create a new password for this user when prompted.

If you want to add more users in the future, just re-run the above command without the -c option (the c is for create):

  1. htpasswd registry.password USERNAME

At this point we have a registry.password file with our users set up and a Docker registry available. You can take a peek at the file at any point if you want to view your users (and remove users if you want to revoke access).

Next, we need to tell Nginx to use that authentication file.

Open up ~/docker-registry/nginx/registry.conf in your favorite text editor:

  1. nano ~/docker-registry/nginx/registry.conf

Scroll to the middle of the file where you’ll see some lines that look like this:

~/docker-registry/nginx/registry.conf
# 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;

Uncomment the two lines that start with auth_basic as well as the line that starts with add_header by removing the # character at the beginning of the lines. It should then look like this:

~/docker-registry/nginx/registry.conf
# 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;

We’ve now told Nginx to enable HTTP basic authentication for all requests that get proxied to the Docker registry and told it to use the password file we just created.

Let’s bring our containers back up to see if authentication is working:

  1. cd ~/docker-registry
  2. docker-compose up

Repeat the previous curl test:

  1. curl http://localhost:5043/v2/

You should get a message complaining about being unauthorized:

Output of curl
<html> <head><title>401 Authorization Required</title></head> <body bgcolor="white"> <center><h1>401 Authorization Required</h1></center> <hr><center>nginx/1.9.7</center> </body> </html>

Now try adding the username and password you created earlier to the curl request:

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

You should get the same output you were getting before — the empty json object {}. You should also see the same registry_ output in the docker-compose terminal.

Go ahead and use CTRL-C in the docker-compose terminal to shut down the Docker containers.

Step 5 — Setting Up SSL

At this point we have the registry up and running behind Nginx with HTTP basic authentication working. However, the setup is still not very secure since the connections are unencrypted. You might have noticed the commented-out SSL lines in the Nginx config file we made earlier.

Let’s enable them. First, open the Nginx configuration file for editing:

  1. nano ~/docker-registry/nginx/registry.conf

Use the arrow keys to move around and look for these lines:

~/docker-registry/nginx/registry.conf
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;

Uncomment the lines below the SSL comment by removing the # characters in front of them. If you have a domain name set up for your server, change the value of server_name to your domain name while you’re at it. When you’re done, the top of the file should look like this:

~/docker-registry/nginx/registry.conf
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;

Save the file. Nginx is now configured to use SSL and will look for the SSL certificate and key files at /etc/nginx/conf.d/domain.crt and /etc/nginx/conf.d/domain.key respectively. Due to the mappings we set up earlier in our docker-compose.yml file the /etc/nginx/conf.d/ path in the Nginx container corresponds to the folder ~/docker-registry/nginx/ on our host machine, so we’ll put our certificate files there.

If you already have an SSL certificate set up or are planning to buy one, then you can just copy the certificate and key files to the paths listed in registry.conf (ssl_certificate and ssl_certificate_key).

You could also get a free signed SSL certificate.

Otherwise we’ll have to use a self-signed SSL certificate.

Signing Your Own Certificate

Since Docker currently doesn’t allow you to use self-signed SSL certificates this is a bit more complicated than usual — we’ll also have to set up our system to act as our own certificate signing authority.

To begin, let’s change to our ~/docker-registry/nginx folder and get ready to create the certificates:

  1. cd ~/docker-registry/nginx

Generate a new root key:

  1. openssl genrsa -out devdockerCA.key 2048

Generate a root certificate (enter whatever you’d like at the prompts):

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

Then generate a key for your server (this is the file referenced by ssl_certificate_key in our Nginx configuration):

  1. openssl genrsa -out domain.key 2048

Now we have to make a certificate signing request.

After you type this command, OpenSSL will prompt you to answer a few questions. Write whatever you’d like for the first few, but when OpenSSL prompts you to enter the “Common Name” make sure to type in the domain or IP of your server.

  1. openssl req -new -key domain.key -out dev-docker-registry.com.csr

For example, if your Docker registry is going to be running on the domain www.ilovedocker.com, then your input should look like this:

Country Name (2 letter code) [AU]:
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:www.ilovedocker.com
Email Address []:

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

Do not enter a challenge password.

Next, we need to sign the certificate request:

  1. openssl x509 -req -in dev-docker-registry.com.csr -CA devdockerCA.crt -CAkey devdockerCA.key -CAcreateserial -out domain.crt -days 10000

Since the certificates we just generated aren’t verified by any known certificate authority (e.g., VeriSign), we need to tell any clients that are going to be using this Docker registry that this is a legitimate certificate. Let’s do this locally on the host machine so that we can use Docker from the Docker registry server itself:

  1. sudo mkdir /usr/local/share/ca-certificates/docker-dev-cert
  2. sudo cp devdockerCA.crt /usr/local/share/ca-certificates/docker-dev-cert
  3. sudo update-ca-certificates

Restart the Docker daemon so that it picks up the changes to our certificate store:

  1. sudo service docker restart

Warning: You’ll have to repeat this step for every machine that connects to this Docker registry! Instructions for how to do this for Ubuntu 14.04 clients are listed in Step 9 — Accessing Your Docker Registry from a Client Machine.

Step 6 — Testing SSL

Bring up our Docker containers via the now familiar docker-compose up:

  1. cd ~/docker-registry
  2. docker-compose up

Do another curl test from another terminal (only this time using https) to verify that our SSL setup is working properly. Keep in mind that for SSL to work correctly you will have to use the same domain name you typed into the Common Name field earlier while you were creating your SSL certificate.

  1. curl https://USERNAME:PASSWORD@[YOUR-DOMAIN]: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

Use the -k option to tell curl not to verify with the peer:

  1. curl -k https://USERNAME:PASSWORD@[YOUR-DOMAIN]:5043/v2/

For example, if the user and password you set up were sammy and test, and your SSL certificate is for www.example.com, then you would type the following:

  1. curl https://sammy:test@www.example.com:5043/v2/

If all went well curl will print an empty json object {}, and your docker-compose terminal will print the usual registry_ output.

If not, recheck the SSL steps and your Nginx configuration file to make sure everything is correct.

At this point we have a functional Docker registry 2.0 up and running behind an Nginx server which is providing authentication and encryption via SSL. If your firewall is configured to allow access to port 5043 from the outside, then you should be able to login to this Docker registry from any machine docker login https://<YOURDOMAIN> and entering the username and password you set in the earlier section.

Step 7 — Setting SSL Port to 443

Just a couple more steps to do before we’re done: change the port to use the standard SSL port of 443 (optional) and set docker-compose up to start this set of containers on startup.

Let’s start by setting up our dockerized Nginx container to listen on port 443 (the standard port for SSL) rather than the non-standard port 5043 we’ve been using so far. Ports below 1024 are “privileged” ports on Linux though, which means we’re going to have to run our docker-compose container as root.

First open up docker-compose.yml in a text editor:

  1. nano ~/docker-registry/docker-compose.yml

Under the Nginx section you’ll see a ports: section, change the - 5043:443 line (this maps port 5043 on our host machine to port 443 inside the Nginx container) to - 443:443 so that our Nginx container’s port 443 gets mapped to our host machine’s port 443. When finished your docker-compose.yml should look like this:

~/docker-registry/docker-compose.yml
nginx:
  image: "nginx:1.9"
  ports:
    - 443:443
  links:
    - registry:registry
  volumes:
    - ./nginx/:/etc/nginx/conf.d:ro
registry:
  image: registry:2
  ports:
    - 127.0.0.1:5000:5000
  environment:
    REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /data
  volumes:
    - ./data:/data

Kill your docker-compose session via CTRL-C if it’s still running, and restart it on port 443:

  1. sudo docker-compose up

Note: Only root users can listen to ports below 1024. Notice that you need to use sudo this time with the docker-compose command so Nginx can run on the default SSL port 443.

You should see docker-compose start up as usual.

Let’s try another curl test using our domain name, only this time we won’t specify the :5043 in the URL:

  1. curl https://<YOURUSERNAME>:<YOURPASSWORD>@YOUR-DOMAIN/v2/

If all went well you should see the usual registry_ output in your docker-compose terminal. You may also want to try running this same curl command from another machine to make sure that your port 443 is being exposed to the outside world.

Go ahead and use CTRL-C in the docker-compose terminal to shut down the Docker containers before moving to the next step.

Step 8 — Starting Docker Registry as a Service

If everything is looking good, let’s go ahead and create an Upstart script so that our Docker registry will start whenever the system boots up.

First let’s remove any existing containers, move our Docker registry to a system-wide location and change its permissions to root:

  1. cd ~/docker-registry
  2. docker-compose rm # this removes the existing containers
  3. sudo mv ~/docker-registry /docker-registry
  4. sudo chown -R root: /docker-registry

Then use your favorite text editor to create an Upstart script:

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

Add the following contents to create the Upstart script (getting Upstart to properly monitor Docker containers is a bit tricky, check out this blog post if you’d like more info about what this Upstart script is doing):

/etc/init/docker-registry.conf
description "Docker Registry"

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

respawn
respawn limit 10 5

chdir /docker-registry

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

For more about Upstart scripts, please read this tutorial.

Let’s test our new Upstart script by running:

  1. sudo service docker-registry start

You should see something like this:

docker-registry start/running, process 25303

You can verify that the server is running by executing:

  1. docker ps

The output should look similar to the following (note that the names all start with dockerregistry_

CONTAINER ID        IMAGE               COMMAND                CREATED             STATUS              PORTS                          NAMES
d4b6fef0b4d1        nginx:1.9           "nginx -g 'daemon of   2 minutes ago       Up 2 minutes        80/tcp, 0.0.0.0:443->443/tcp   dockerregistry_nginx_1
77668352bd39        registry:2          "registry cmd/regist   2 minutes ago       Up 2 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. For our final test let’s “live-watch” the log file with tail (the sudo is necessary because upstart logs are written as the root user):

  1. sudo tail -f /var/log/upstart/docker-registry.log

You should see the usual registry_ output. From another terminal or machine go ahead and run our now familiar curl test:

  1. curl https://<YOUR_USERNAME>:<YOURPASSWORD>@[YOUR-DOMAIN]/v2/

If everything is working correctly curl will print a {} to your terminal and you should see the usual:

registry_1 | time="2015-08-12T08:01:12.241887501Z" level=debug msg="authorizing request" environment=development http.request.host=docker.meatflavoredbeer.com http.request.id=e8d69e16-9448-4c48-afd8-57b1f1302742 http.request.method=GET http.request.remoteaddr=106.1.247.4 http.request.uri="/v2/" http.request.useragent="curl/7.37.1" instance.id=14d4727b-fda1-463f-8d0e-181f4c70cb17 service=registry version=v2.0.1
registry_1 | time="2015-08-12T08:01:12.242206499Z" level=info msg="response completed" environment=development http.request.host=docker.meatflavoredbeer.com http.request.id=e8d69e16-9448-4c48-afd8-57b1f1302742 http.request.method=GET http.request.remoteaddr=106.1.247.4 http.request.uri="/v2/" http.request.useragent="curl/7.37.1" http.response.contenttype="application/json; charset=utf-8" http.response.duration=3.359883ms http.response.status=200 http.response.written=2 instance.id=14d4727b-fda1-463f-8d0e-181f4c70cb17 service=registry version=v2.0.1
registry_1 | 172.17.0.4 - - [12/Aug/2015:08:01:12 +0000] "GET /v2/ HTTP/1.0" 200 2 "" "curl/7.37.1"
nginx_1    | 106.1.247.4 - nik [12/Aug/2015:08:01:12 +0000] "GET /v2/ HTTP/1.1" 200 2 "-" "curl/7.37.1" "-"

Step 9 — Accessing Your Docker Registry from a Client Machine

To access your Docker registry from another machine, first add the SSL certificate you created earlier to the new client machine. The file you want is located at ~/docker-registry/nginx/devdockerCA.crt.

You can copy it to the new machine directly or use the below instructions to copy and paste it:

On the registry server, view the certificate:

  1. sudo cat /docker-registry/nginx/devdockerCA.crt

You’ll get output that looks something like this:

Output of sudo cat /docker-registry/nginx/devdockerCA.crt
-----BEGIN CERTIFICATE----- MIIDXTCCAkWgAwIBAgIJANiXy7fHSPrmMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX aWRnaXRzIFB0eSBMdGQwHhcNMTQwOTIxMDYwODE2WhcNNDIwMjA2MDYwODE2WjBF MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50 ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB CgKCAQEAuK4kNFaY3k/0RdKRK1XLj9+IrpR7WW5lrNaFB0OIiItHV9FjyuSWK2mj ObR1IWJNrVSqWvfZ/CLGay6Lp9DJvBbpT68dhuS5xbVw3bs3ghB24TntDYhHMAc8 GWor/ZQTzjccHUd1SJxt5mGXalNHUharkLd8mv4fAb7Mh/7AFP32W4X+scPE2bVH OJ1qH8ACo7pSVl1Ohcri6sMp01GoELyykpXu5azhuCnfXLRyuOvQb7llV5WyKhq+ SjcE3c2C+hCCC5g6IzRcMEg336Ktn5su+kK6c0hoD0PR/W0PtwgH4XlNdpVFqMST vthEG+Hv6xVGGH+nTszN7F9ugVMxewIDAQABo1AwTjAdBgNVHQ4EFgQULek+WVyK dJk3JIHoI4iVi0FPtdwwHwYDVR0jBBgwFoAULek+WVyKdJk3JIHoI4iVi0FPtdww DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAkignESZcgr4dBmVZqDwh YsrKeWSkj+5p9eW5hCHJ5Eg2X8oGTgItuLaLfyFWPS3MYWWMzggxgKMOQM+9o3+k oH5sUmraNzI3TmAtkqd/8isXzBUV661BbSV0obAgF/ul5v3Tl5uBbCXObC+NUikM O0C3fDmmeK799AM/hP5CTDehNaFXABGoVRMSlGYe8hZqap/Jm6AaKThV4g6n4F7M u5wYtI9YDMsxeVW6OP9ZfvpGZW/n/88MSFjMlBjFfFsorfRd6P5WADhdfA6CBECG LP83r7/MhqO06EOpsv4n2CJ3yoyqIr1L1+6C7Erl2em/jfOb/24y63dj/ATytt2H 6g== -----END CERTIFICATE-----

Copy that output to your clipboard, and connect to your client machine.

On the client machine, create the certificate directory:

  1. sudo mkdir /usr/local/share/ca-certificates/docker-dev-cert

Open the certificate file for editing:

  1. sudo nano /usr/local/share/ca-certificates/docker-dev-cert/devdockerCA.crt

Paste the certificate contents.

Verify that the file saved to the client machine correctly by viewing the file:

  1. cat /usr/local/share/ca-certificates/docker-dev-cert/devdockerCA.crt

If everything worked properly you’ll see the same text from earlier:

Output of cat /usr/local/share/ca-certificates/docker-dev-cert/devdockerCA.crt
-----BEGIN CERTIFICATE----- MIIDXTCCAkWgAwIBAgIJANiXy7fHSPrmMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV ... ... LP83r7/MhqO06EOpsv4n2CJ3yoyqIr1L1+6C7Erl2em/jfOb/24y63dj/ATytt2H 6g== -----END CERTIFICATE-----

Now update the certificates:

  1. sudo update-ca-certificates

You should get output that looks like the following (note the1 added):

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

If you don’t have Docker installed on the client yet, do so now (see the Prerequisites section).

Restart Docker to make sure it reloads the system’s CA certificates.

  1. sudo service docker restart

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

  1. docker login https://YOUR-DOMAIN

Note that you’re using https://. Enter the username and password you set up earlier (enter whatever you’d like for email if prompted).

Output of docker login
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

At this point your Docker registry is up and running! Let’s make a test image to push to the registry.

Step 10 — Publish to Your Private Docker Registry

You are now ready to publish an image to your private Docker registry, but first we have to create an image. We will create a simple image based on the ubuntu image from Docker Hub.

From your client machine, create a small empty image to push to our new registry.

  1. docker run -t -i ubuntu /bin/bash

After it finishes downloading you’ll be inside a Docker prompt. Let’s make a quick change to the filesystem by creating a file called SUCCESS:

  1. touch /SUCCESS

Exit out of the Docker container:

  1. exit

Commit the change:

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

This command creates a new image called test-image based on the image already running plus any changes you have made. In our case, the addition of the /SUCCESS file is included in the new image.

This image only exists locally right now, so let’s push it to the new registry we’ve created.

In the previous step, you logged into your private Docker registry. In case you aren’t still logged in, let’s log in again (note that you want to use https://):

  1. docker login https://YOUR-DOMAIN

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.

Docker has an unusual mechanism for specifying which registry to push to. You have to tag an image with the private registry’s location in order to push to it. Let’s tag our image to our private registry:

  1. docker tag test-image [YOUR-DOMAIN]/test-image

Note that you are using the local name of the image first, then the tag you want to add to it. The tag does not use https://, just the domain, port, and image name.

Now we can push that image to our registry. This time we’re using the tag name only:

  1. docker push [YOUR-DOMAIN]/test-image

This will take a moment to upload to the registry server. You should see output that ends with something similar to the following:

Output of docker push
latest: digest: sha256:5ea1cfb425544011a3198757f9c6b283fa209a928caabe56063f85f3402363b4 size: 8008

Step 11 — Pull from Your Docker Registry

To make sure everything worked, let’s go back to our original server (where you installed the Docker registry) and pull the image we just pushed from the client. You could also test this from a third server.

If Docker is not installed on your test pull server, go back and follow the installation instructions (and if it’s a third server, the SSL instructions) from Step 6.

Log in with the username and password you set up previously.

  1. docker login https://[YOUR-DOMAIN]

And now pull the image. You want just the “tag” image name, which includes the domain name, port, and image name (but not https://):

  1. docker pull [YOUR-DOMAIN]/test-image

Docker will do some downloading and return you to the prompt. If you run the image on the new machine you’ll see that the SUCCESS file we created earlier is there:

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

List your files inside the bash shell:

  1. ls

You should see the SUCCESS file we created earlier for this image:

SUCCESS  bin  boot  dev  etc  home  lib  lib64  media   mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var

Conclusion

Congratulations! You’ve just used your own private Docker registry to push and pull your first Docker container!

Happy Dockering!

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about our products

About the authors

Default avatar
Tammy Fox

editor


Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
75 Comments
Leave a comment...

This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

This is a great tutorial. Thanks a lot !!!

Indeed, I just completed it as well. Thanks a lot!

hi, i have not been able to login.

2014/11/03 20:44:11 Error response from daemon: Invalid Registry endpoint: Get https:/mydomain.com:8080/v1/_ping: x509: certificate signed by unknown authority```

I am getting this same error. Did you figure this out?

no sir. i have to double check the certificates as i have used my own. i might try to bundle .key and .crt files into a single .pem for another try.

In Docker 1.3.1 they added the --insecure-registry flag. Here is how it works for me:

sudo docker --insecure-registry=docker-registry.stackengine.com:8080 login https://docker-registry.stackengine.com:8080

@paassalles When you try to login, you should use the same domain you used on

Step 5 |------> Signing Your Own Certificate section

when you ran:

openssl req -new -key dev-docker-registry.com.key -out dev-docker-registry.com.csr

on this particular question:

Common Name (e.g. server FQDN or YOUR name) []:www.ilovedocker.com

Did you use “mydomain.com” ?

@ernestojic

I followed those instructions with the “mydomain.com” . I can successfully use curl, however docker login is where things do not work. I’m currently looking into https://github.com/docker/docker-registry/issues/82 - might be related here.

@behmphi hmm interesting, looks like that is a very new release. I may check into this as well

Hi, my docker-registry server is on ubuntu VM and client docker is on boot2docker, over windows platform. I followed the instruction and came across the problem as above. Then, I tried to copy the content of files (devdockerCA.crt devdockerCA.key dev-docker-registry.com.crt dev-docker-registry.com.csr dev-docker-registry.com.key) in folder ~/certs/ in docker-registry server append to file /usr/local/etc/ssl/certs/ca-certificates.crt in boot2docker rather than container. In the end, restart docker, try to docker login, it is successful this time! And I also push and pull imges successfully! Hope this would help you! Good luck!

thanks for the remark @ernestojic, it was the correct domain in place.

i still get 2014/11/04 23:17:38 Error response from daemon: Invalid Registry endpoint: Get https://mydomain:8080/v1/_ping: x509: certificate signed by unknown authority

Kamal Nasser
DigitalOcean Employee
DigitalOcean Employee badge
November 5, 2014

Because your certificate is self-signed, you will need to pass --insecure-registry to docker login, see @behemphi’s comment below.

I follow the steps and fail at “sudo service nginx restart”. The size of /var/log/nginx/access.log and error.log is 0. I always fail to start the nginx server. Any possible clues? How to check it?

Kamal Nasser
DigitalOcean Employee
DigitalOcean Employee badge
November 6, 2014

Try running nginx manually:

sudo nginx -c /etc/nginx/nginx.conf

Does that output any errors?

I found the problem. I need to run curl with sudo then I can get the result same as you. Thanks!

Now I have issue when I try to push image. (I don’t config SSL for nginx). I can run “docker login …” and “docker tag …”. When I run “docker push …”, I get the output below: The push refers to a repository [ <registry server>:9090/java7tomcat7sm934web] (len: 1) Sending image list Pushing repository <registry server>:9090/java7tomcat7sm934web (1 tags)

2014/11/07 11:22:38 HTTP code 401, Docker will not send auth headers over HTTP.

And I check the /var/log/nginx/error.log, found the error message as follow:

2014/11/07 11:22:36 [error] 32756#0: *13 connect() failed (111: Connection refused) while connecting to upstream, client: <client IP>, server: <registry server>, request: “GET /v1/_ping HTTP/1.1”, upstream: “http://[::1]:5000/v1/_ping”, host: " <registry server>:9090" 2014/11/07 11:22:37 [error] 32756#0: *17 no user/password was provided for basic authentication, client: 16.153.99.8, server: <registry server>, request: “GET /v1/images/511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158/json HTTP/1.1”, host: “16.186.75.65:9090” 2014/11/07 11:22:38 [error] 32756#0: *18 no user/password was provided for basic authentication, client: 16.153.99.8, server: 16.186.75.65, request: “PUT /v1/images/511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158/json HTTP/1.1”, host: " <registry server>:9090"

In my test environment, the docker client and registry server are the same host. I config environment variable http_proxy also. So, in the error message, the <client IP> is the proxy address. I remove the environment variable, and restart the docker, docker-registry and nginx service. The error still exists. Now the client IP changes to 127.0.0.1.

Besides, why is the error “no user/password was provided for basic authentication”?

Thanks

I already solve it by setting up SSL for nginx as SSL is mandatory.

@passinger How to use SSL in mandatory?

Addendum:

First thanks for an amazing post.

My previous comment related directly to the use of a self signed certificate.

When I decided to use a legitimate cert, I could not get things to work. docker login would fail with this message:

boyd@my-repo:~ 16:14:02 >docker login https://docker-repository.se.com:8080
Username: fooserName
Password: xkcdcom936
Email: boyd@se.com
2014/11/09 16:16:51 Error response from daemon: Invalid registry endpoint https://docker-registry.se.com:8080/v1/: Get https://docker-registry.se.com:8080/v1/_ping: x509: certificate signed by unknown authority. If this private registry supports only HTTP or HTTPS with an unknown CA certificate, please add `--insecure-registry docker-registry.se.com:8080` to the daemon's arguments. In the case of HTTPS, if you have access to the registry's CA certificate, no need for the flag; simply place the CA certificate at /etc/docker/certs.d/docker-registry.se.com:8080/ca.crt

The problem here was made apparent in the nginx log:

2014/11/10 03:38:51 [error] 12836#0: *85 no user/password was provided for basic authentication, client: 66.249.65.64, server: docker-registry.se.com, request: "GET / HTTP/1.1", host: "docker-registry.se.com:8080"

So this worked without using -insecure-registry

docker login https://fooserName:xkcdcom936@docker-registry.se.com:8080

Which is to say you have to pass the basic auth credentials with the login.

Hope that saves you all some time.

There’s no update-ca-certificates commnad on centos6. I use update-ca-trust instead but it do not work:

cp devdockerCA.crt /etc/pki/ca-trust/source/anchors/
cp devdockerCA.crt /etc/pki/ca-trust/extracted/openssl/
cp devdockerCA.crt /etc/pki/ca-trust/extracted/

No one take effect :(

Hi, I met same problem with u. I solved that with : $ yum install ca-certificates $ update-ca-trust force-enable # force update $ cp devdockerCA.crt /etc/pki/ca-trust/source/anchors/ $ update-ca-trust extract # update $ service docker restart

Take service docker.io restart before docker login. I think docker does not know about new certificates.

thanks man! This is the trick is docker is already running on the client machine :)

Doesn’t nginx have to listen on port 443 in order for https to work? When I curl a private registry with this setup, I get:

port 443: Connection refused

For some reason this is not working with the latest release of docker-registry (0.9.0). After I execute gunicorn --access-logfile - --debug -k gevent -b 0.0.0.0:5000 -w 1 docker_registry.wsgi:application I don’t get any arrero message though I can confirm that this was working with version (0.8.1).

Is there any way to install an older version of the registry? Or a way to solve this?

I think adding the option --preload would solve your problem. As far as running specific versions goes, I think running the registry as a container is easily the best way to do this, e.g. docker run -d -p 5000:5000 registry:0.8.1. See https://registry.hub.docker.com/_/registry/

Thanks, this is brilliant.

It might be worth noting in steps 1-3 can be simplified by using the containerized version of registry rather than installing it locally. e.g. docker run -p 5000:5000 -e GUNICORN_OPTS=[--preload] registry works for me in place of those steps (Note that the -e option seems to be necessary due to a bug in the current (0.9.0) version as documented on the Hub, https://registry.hub.docker.com/_/registry/).

Makes me think I should also be able to run the nginx in a container as well, but haven’t been able to get that working. (I suppose the certificate dance must be done locally in any event?)

Is it faster to pull from the registry if your on the same server as the registry?

Cheers!

On centos6.5. curl localhost:5000. It could work well. But curl username:password@localhost:8080 . I can not got right message.

it will print

<html> <head><title>502 Bad Gateway</title></head> <body bgcolor=“white”> <center><h1>502 Bad Gateway</h1></center> <hr><center>nginx/1.6.2</center> </body> </html>

Anyone can help me

Great Job. Well documented. Every thing worked like a champ.

Great Job. Well documented. Every thing worked like a champ.

Sorry for the double comment above.

Thought I would add that if you want to have persistent storage for your remote repo, you have to change the config.yml on the remote repo. You have to use the same <directory-path>/config.yml file you found in the Step 2 above.
In my case it was:

remote_repo:/usr/local/lib/python2.7/dist-packages/config# ls -l
total 32
-rw-r--r--  1 root staff   73 Dec  7 10:54 boto.cfg
-rw-r--r--  1 root staff  351 Dec  7 10:54 config_mirror.yml
-rw-r--r--  1 root staff 7095 Dec  7 10:54 config_sample.yml
-rw-r--r--  1 root staff 7186 Dec  8 15:31 config.yml

Modify the config.yml in the “sqlalchemy_index_database” section and then the “local” section, as shown highlighted below.

Edit the config.yml in the “common” section below, add the highlighted line:

 # SQLite search backend
 # sqlalchemy_index_database: _env:SQLALCHEMY_INDEX_DATABASE:sqlite:////tmp/docker-registry.db
    sqlalchemy_index_database: sqlite://///home/registry/docker-registry.db

You will obviously have to change the “/home/registry/” directory to your specific directory on host machine. (watch out for wordwrap above)

In the section below, change the local section to look like: modifying the highlighted lines

local: &local
    <<: *common
    storage: file
    storage_path: /home/registry

Then restart your service In my case it was: service docker-registry start

Thank you for this nice tutorial. I setup the private repository on Ubuntu 14.04 but I am not getting how to access this Registry from CoreOS. Could you please help me on this ?

Hi, Does this work on ubuntu-12.04.4-desktop-amd64? I just tired and its spitting a lot of error. Below is when i tried to start the registry, right after “sudo -E pip install docker-registry”

 → gunicorn --access-logfile - --debug -k gevent -b 0.0.0.0:5000 -w 1 docker_registry.wsgi:application

Error: class uri 'gevent' invalid or not found: 

[Traceback (most recent call last):
  File "/usr/local/lib/python2.7/dist-packages/gunicorn/util.py", line 139, in load_class
    mod = import_module('.'.join(components))
  File "/usr/lib/python2.7/importlib/__init__.py", line 37, in import_module
    __import__(name)
  File "/usr/local/lib/python2.7/dist-packages/gunicorn/workers/ggevent.py", line 22, in <module>
    raise RuntimeError("You need gevent installed to use this worker.")
RuntimeError: You need gevent installed to use this worker.
]

Thanks

Andrew SB
DigitalOcean Employee
DigitalOcean Employee badge
January 13, 2015

@okram9999 It looks like you are missing a dependency. Install gevent with:

sudo apt-get install python-gevent

Hi,

I’m trying to install this on a fresh install of ubuntu 14.04.

I’m running into problems when executing:

sudo pip install docker-registry

it fails, this seems to be the reason:

...
building 'M2Crypto.__m2crypto' extension

swigging SWIG/_m2crypto.i to SWIG/_m2crypto_wrap.c

swig -python -I/usr/include/python2.7 -I/usr/include/x86_64-linux-gnu -I/usr/include -I/usr/include/openssl -includeall -modern -o SWIG/_m2crypto_wrap.c SWIG/_m2crypto.i

SWIG/_m2crypto.i:30: Error: Unable to find 'openssl/opensslv.h'

SWIG/_m2crypto.i:33: Error: Unable to find 'openssl/safestack.h'

SWIG/_evp.i:12: Error: Unable to find 'openssl/opensslconf.h'

SWIG/_ec.i:7: Error: Unable to find 'openssl/opensslconf.h'

error: command 'swig' failed with exit status 1

Any ideas? I tried to google it, but I didn’t find any solution :/

(openssl is installed)

Here’s the complete pip.log if it helps: https://dl.dropboxusercontent.com/u/7438365/pip.log

BR Daniel

Hi, First of all thanks for the great step-by-step blog post. @ninjafox you need to install package swig. Just run “apt-get install swig”.

Br, Micek

Hi,

Thanks, but I’ve done that already, because the first time it said it couldn’t find swig, so I installed it. But now I’m stuck with this error.

edit: I’ve finally found a similar error via google and it’s fixed by installing “libssl-dev”

Now it seems to have completed successfully but it still showed a lot of error messages from swig…

BR Daniel

This comment has been deleted

    I have just finished the whole process with success in ubuntu-trusty-amd64 server edition :) So maybe I can help you in some steps.

    BR, Micek

    I don’t know, they didn’t end up in the pip.log. Just a lot of “warnings” about swig not finding some files.

    When I try to run the pip docker registry install again it just lists all the requirements as already satisfied and cleans up.

    When I try to execute the next step in the guide:

    gunicorn --access-logfile - --debug -k gevent -b 0.0.0.0:5000 -w 1 docker_registry.wsgi:application   
    
    

    nothing happens at all.

    BR Daniel

    Hm I’m using the desktop version of 14.04 LTS… maybe that’s the reason.

    BR Daniel

    If “sudo pip install docker-registry” returns with exit code 0, I think it is successfully installed. You can go to the next step.

    With executing the gunicorn command you just search for the place of the “config_sample.yml” file. The file is here: /usr/local/lib/python2.7/dist-packages/config/config_sample.yml

    Ok thanks. That “gunicorn” command doesn’t work for me for whatever reason. It doesn’t return anything. I’ve also tried it on an ubuntu 14.04 server to make sure it’s not caused by the desktop version which I’m running locally (before executing the pip install I installed the openssl-dev and swig package) The gunicorn command doesn’t work there either as described here.

    I just went into that folder you posted and followed through with the next steps, copying the config.yml etc.

    The registry is running now as it should I think, I can access it under localhost:5000

    Thanks for your help, I’ll continue tomorrow ;-)

    Good night & BR Daniel

    Hi, I also needed to install libssl-dev on a Ubuntu 14.04.1 trusty: apt-get install libssl-dev

    Cheers, Moshe Nadler

    For some reason this is not working with the latest release of docker-registry (0.9.0). After I execute gunicorn --access-logfile - --debug -k gevent -b 0.0.0.0:5000 -w 1 docker_registry.wsgi:application I don’t get any arrero message though I can confirm that this was working with version (0.8.1).

    Is there any way to install an older version of the registry? Or a way to solve this?

    I think adding the option --preload would solve your problem. As far as running specific versions goes, I think running the registry as a container is easily the best way to do this, e.g. docker run -d -p 5000:5000 registry:0.8.1. See https://registry.hub.docker.com/_/registry/

    Thanks, this is brilliant.

    It might be worth noting in steps 1-3 can be simplified by using the containerized version of registry rather than installing it locally. e.g. docker run -p 5000:5000 -e GUNICORN_OPTS=[--preload] registry works for me in place of those steps (Note that the -e option seems to be necessary due to a bug in the current (0.9.0) version as documented on the Hub, https://registry.hub.docker.com/_/registry/).

    Makes me think I should also be able to run the nginx in a container as well, but haven’t been able to get that working. (I suppose the certificate dance must be done locally in any event?)

    Is it faster to pull from the registry if your on the same server as the registry?

    Cheers!

    On centos6.5. curl localhost:5000. It could work well. But curl username:password@localhost:8080 . I can not got right message.

    it will print

    <html> <head><title>502 Bad Gateway</title></head> <body bgcolor=“white”> <center><h1>502 Bad Gateway</h1></center> <hr><center>nginx/1.6.2</center> </body> </html>

    Anyone can help me

    Great Job. Well documented. Every thing worked like a champ.

    Great Job. Well documented. Every thing worked like a champ.

    Sorry for the double comment above.

    Thought I would add that if you want to have persistent storage for your remote repo, you have to change the config.yml on the remote repo. You have to use the same <directory-path>/config.yml file you found in the Step 2 above.
    In my case it was:

    remote_repo:/usr/local/lib/python2.7/dist-packages/config# ls -l
    total 32
    -rw-r--r--  1 root staff   73 Dec  7 10:54 boto.cfg
    -rw-r--r--  1 root staff  351 Dec  7 10:54 config_mirror.yml
    -rw-r--r--  1 root staff 7095 Dec  7 10:54 config_sample.yml
    -rw-r--r--  1 root staff 7186 Dec  8 15:31 config.yml
    

    Modify the config.yml in the “sqlalchemy_index_database” section and then the “local” section, as shown highlighted below.

    Edit the config.yml in the “common” section below, add the highlighted line:

     # SQLite search backend
     # sqlalchemy_index_database: _env:SQLALCHEMY_INDEX_DATABASE:sqlite:////tmp/docker-registry.db
        sqlalchemy_index_database: sqlite://///home/registry/docker-registry.db
    

    You will obviously have to change the “/home/registry/” directory to your specific directory on host machine. (watch out for wordwrap above)

    In the section below, change the local section to look like: modifying the highlighted lines

    local: &local
        <<: *common
        storage: file
        storage_path: /home/registry
    

    Then restart your service In my case it was: service docker-registry start

    Thank you for this nice tutorial. I setup the private repository on Ubuntu 14.04 but I am not getting how to access this Registry from CoreOS. Could you please help me on this ?

    Hi, Does this work on ubuntu-12.04.4-desktop-amd64? I just tired and its spitting a lot of error. Below is when i tried to start the registry, right after “sudo -E pip install docker-registry”

     → gunicorn --access-logfile - --debug -k gevent -b 0.0.0.0:5000 -w 1 docker_registry.wsgi:application
    
    Error: class uri 'gevent' invalid or not found: 
    
    [Traceback (most recent call last):
      File "/usr/local/lib/python2.7/dist-packages/gunicorn/util.py", line 139, in load_class
        mod = import_module('.'.join(components))
      File "/usr/lib/python2.7/importlib/__init__.py", line 37, in import_module
        __import__(name)
      File "/usr/local/lib/python2.7/dist-packages/gunicorn/workers/ggevent.py", line 22, in <module>
        raise RuntimeError("You need gevent installed to use this worker.")
    RuntimeError: You need gevent installed to use this worker.
    ]
    

    Thanks

    Andrew SB
    DigitalOcean Employee
    DigitalOcean Employee badge
    January 13, 2015

    @okram9999 It looks like you are missing a dependency. Install gevent with:

    sudo apt-get install python-gevent
    

    Hi,

    I’m trying to install this on a fresh install of ubuntu 14.04.

    I’m running into problems when executing:

    sudo pip install docker-registry
    

    it fails, this seems to be the reason:

    ...
    building 'M2Crypto.__m2crypto' extension
    
    swigging SWIG/_m2crypto.i to SWIG/_m2crypto_wrap.c
    
    swig -python -I/usr/include/python2.7 -I/usr/include/x86_64-linux-gnu -I/usr/include -I/usr/include/openssl -includeall -modern -o SWIG/_m2crypto_wrap.c SWIG/_m2crypto.i
    
    SWIG/_m2crypto.i:30: Error: Unable to find 'openssl/opensslv.h'
    
    SWIG/_m2crypto.i:33: Error: Unable to find 'openssl/safestack.h'
    
    SWIG/_evp.i:12: Error: Unable to find 'openssl/opensslconf.h'
    
    SWIG/_ec.i:7: Error: Unable to find 'openssl/opensslconf.h'
    
    error: command 'swig' failed with exit status 1
    
    

    Any ideas? I tried to google it, but I didn’t find any solution :/

    (openssl is installed)

    Here’s the complete pip.log if it helps: https://dl.dropboxusercontent.com/u/7438365/pip.log

    BR Daniel

    Hi, First of all thanks for the great step-by-step blog post. @ninjafox you need to install package swig. Just run “apt-get install swig”.

    Br, Micek

    Hi,

    Thanks, but I’ve done that already, because the first time it said it couldn’t find swig, so I installed it. But now I’m stuck with this error.

    edit: I’ve finally found a similar error via google and it’s fixed by installing “libssl-dev”

    Now it seems to have completed successfully but it still showed a lot of error messages from swig…

    BR Daniel

    This comment has been deleted

      I have just finished the whole process with success in ubuntu-trusty-amd64 server edition :) So maybe I can help you in some steps.

      BR, Micek

      I don’t know, they didn’t end up in the pip.log. Just a lot of “warnings” about swig not finding some files.

      When I try to run the pip docker registry install again it just lists all the requirements as already satisfied and cleans up.

      When I try to execute the next step in the guide:

      gunicorn --access-logfile - --debug -k gevent -b 0.0.0.0:5000 -w 1 docker_registry.wsgi:application   
      
      

      nothing happens at all.

      BR Daniel

      Hm I’m using the desktop version of 14.04 LTS… maybe that’s the reason.

      BR Daniel

      If “sudo pip install docker-registry” returns with exit code 0, I think it is successfully installed. You can go to the next step.

      With executing the gunicorn command you just search for the place of the “config_sample.yml” file. The file is here: /usr/local/lib/python2.7/dist-packages/config/config_sample.yml

      Ok thanks. That “gunicorn” command doesn’t work for me for whatever reason. It doesn’t return anything. I’ve also tried it on an ubuntu 14.04 server to make sure it’s not caused by the desktop version which I’m running locally (before executing the pip install I installed the openssl-dev and swig package) The gunicorn command doesn’t work there either as described here.

      I just went into that folder you posted and followed through with the next steps, copying the config.yml etc.

      The registry is running now as it should I think, I can access it under localhost:5000

      Thanks for your help, I’ll continue tomorrow ;-)

      Good night & BR Daniel

      Hi, I also needed to install libssl-dev on a Ubuntu 14.04.1 trusty: apt-get install libssl-dev

      Cheers, Moshe Nadler

      Command: sudo docker push 127.0.0.1:5000/ubuntu

      Error: FATA[0010] Index response didn’t contain an access token

      any idea?

      we are missing two libraries here: swig and libssl-dev sudo apt-get install swig libssl-dev

      Great tutorial, well done!-) I have a question you may be able to answer when it’s convenient for you: What would be the best/most efficient way of creating multiple accounts for the repo where each user has a sandboxed account and can push/pull at will? Would there need to be accounts setup on the Ubuntu VPS itself for each user or is there a way to have users within the repo but not necessarily on the VPS system? Each user would need read/write to their repo account but NOT any admin privs on the server itself either way.

      many thanks

      hi there! I have tried to follow the above steps and set up a private docker registry of my own. The steps 1 and 2 work well for me. Post that I am facing the below problems. Hope to receive comments and suggestions …

      • I haven’t been able to run docker-registry as a service. It ends up saying “Unknown job : docker-registry”. In spite of the above I am able to push images to the set up registry.
      • During ‘docker login <registry url>’, how do we make sure that the login is happening to the private registry only? Because on every execution of the login command, it asks for username, password and email even though I haven’t specifically set up the email id for the user.

      I just tried your instructions here using a freshly built Ubuntu 14.04 VM (minimal server). The prerequisites you listed were not quite enough to successfully use pip to install docker-registry in Step 2.

      To resolve this, I had to install two additional prerequisites through apt-get:

      • swig
      • libssl-dev

      Also, in Step 2 when I initially tried the gunicorn command I received no output. There is no *.yml file in the docker-registry directory under dist-packages.

      The path that I had was a bit different: /usr/local/lib/python2.7/dist-packages/config/config_sample.yml

      Hopefully this can be helpful to someone.

      Hi, Great post, was looking for exactly same :) Just have one question, If I dont want to set SSL certificate to listen on https , can I push my images directly to docker registry running on “http://localhost:8080” , I have set up everything fine but in this case when I push any image using below command: $docker push localhost:8080/mytest-image

      I always get “FATA[0000] HTTP code 401, Docker will not send auth headers over HTTP.” why is that so? Thanks a lot in advance.

      ~Yash

      I followed above steps with self signed cert I get following error with https but with http it works fine. Any additional step required ? :

      root@docker-repo-01:~/certs# curl https://local:passw0rd@docker-repo-01.dbplat.altus.bblabs.rim.net:8080 curl: (35) error:140770FC:SSL routines:SSL23_GET_SERVER_HELLO:unknown protocol

      root@docker-repo-01:~/certs# curl http://local:passw0rd@docker-repo-01.dbplat.altus.bblabs.rim.net:8080 ""docker-registry server""root@docker-repo-01:~/certs#

      Just what I needed. Thanks !!

      This comment has been deleted

        Hi, As per above has been done successfully. I am able to login in docker registry with https successfully. Able to tag commit but not able to push a image on docker- registry. Getting below error.

        $ docker push https://abc.dockerregistry.com:8080/test1 FATA[0000] Invalid repository name (ex: “registry.domain.tld/myrepos”)

        Please suggest.

        Well, there is no https:// in front of your domain name, and you just tag your image like this: abc.dockerregistry.com:8080/test1, so the you’d better use this command: docker push abc.dockerragistry.com:8080/test1. Hope this would help you!

        Very thorough and helpful post. Thanks!!

        Great walk-through, got my registry set up with only a few hitches that came from not following the steps right :/

        Is there any reason to use a custom port for ssl instead of the default port?

        Great walk-through, got my registry set up with only a few hitches that came from not following the steps right :/

        Is there any reason to use a custom port for ssl instead of the default port?

        I am having trouble pushing to the custom port as docker seems to default to 443

        Is it possible to start PUBLIC docker hub? In case I don’t want to play with certificates/logins/etc? It could be very useful.

        Thanks in advance.

        PS: This guide is really nice. Thank you very much!

        I mean that authorization is needed only for pushes. Pulling and searching don’t require login.

        #awesome digitalocean community power!# thx !!!

        when i try to login (with or without --insecure-registry flag) i get this error:

        FATA[0004] Error response from daemon: v1 ping attempt failed with error: Get https://registry:8080/v1/_ping: x509: cannot validate certificate for registry because it doesn’t contain any IP SANs. If this private registry supports only HTTP or HTTPS with an unknown CA certificate, please add --insecure-registry registry:8080 to the daemon’s arguments. In the case of HTTPS, if you have access to the registry’s CA certificate, no need for the flag; simply place the CA certificate at /etc/docker/certs.d/registry:8080/ca.crt

        also i tried to copy/create a crt file at the given location - but still get this error… when i try https://registry:8080 in firefox it works…

        System: Ubuntu 14.04.2 LTS DockerClient: 1.5.0

        Any chance this article can be updated for setting up Docker Registry 2.0? It’s a Golang implementation now instead of Python - https://github.com/docker/distribution/

        Any leads on this? Would be great to be able to set the new Registry up in a similar way to that which was demonstrated here!

        in the file /etc/nginx/sites-available/docker-registry , can I write the IP address of the host as the server_name, as it does not have a domain name and the registry used for the internal use of the company. It is a VM.

        Kamal Nasser
        DigitalOcean Employee
        DigitalOcean Employee badge
        June 22, 2015

        Yes, you can use the IP address as the server_name – an IP address is a valid hostname :)

        How to set the context path ? I want to access my private docker repository using url like www.apps.xxxxx.com/docker.

        Any suggestion please.

        Please beware that this tutorial is outdated. Docker no longer support the registry implemented in Python. They have created a Docker image containing the registry. You can learn more about it in https://docs.docker.com/registry/deploying/.

        Great tutorial, you guys are awesome!!!

        The install in step one needs “swig” and “libssl-dev” as well (I ran it on the docker application).

        How can we see the images in the private registry via secured https or http?

        This is really awesome. Thank you.

        Hi, Thank you for this great tutorial. I followed the tutorial and I could use the private registry through a linux machine. But I had problem in connecting through a Windows machine using toolbox docker (ancienment boot2docker) to connecte to my private Docker registry. Have you an idea on how I can configure the Windows client to connect to my private Docker registry?

        Thanks Youmarva

        Thank you for the awesome tutorial. This was my first private registry deployment and this guide made it very clear.

        I’m having some difficulties though, when I try to run images. My server is (fake name) master.dev.local. Service is running fine:

        me@master:~$ sudo docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES e8625962d0f5 nginx:1.9 “nginx -g 'daemon off” 3 hours ago Up 3 hours 80/tcp, 0.0.0.0:50443->443/tcp dockerregistry_nginx_1 f1240f752ffb registry:2.1.1 “/bin/registry /etc/d” 3 hours ago Up 3 hours 127.0.0.1:5000->5000/tcp dockerregistry_registry_1

        if I curl it from localhost, i see my image and tags

        curl -v -X GET http://localhost:5000/v2/integral/tags/list

        • Hostname was NOT found in DNS cache
        • Trying ::1…
        • connect to ::1 port 5000 failed: Connection refused
        • Trying 127.0.0.1…
        • Connected to localhost (127.0.0.1) port 5000 (#0)

        GET /v2/integral/tags/list HTTP/1.1 User-Agent: curl/7.35.0 Host: localhost:5000 Accept: /

        < HTTP/1.1 200 OK < Content-Type: application/json; charset=utf-8 < Docker-Distribution-Api-Version: registry/2.0 < Date: Tue, 19 Jan 2016 16:55:44 GMT < Content-Length: 56 < **{“name”:“integral”,“tags”:[“trusty”,“wheezy”,“latest”]} *** Connection #0 to host localhost left intact

        I can creat, tag and push normally, but when I try to run, I get the error message:

        me@worsktation:/$ sudo docker run -it master.dev.local:50443/integral:trusty Unable to find image ‘master.dev.local:50443/integral:trusty’ locally trusty: Pulling from integral

        fcee8bcfe180: Pull complete 4cdc0cbc1936: Pull complete d9e545b90db8: Pull complete c4bea91afef3: Pull complete 44eb9662ae92: Pull complete 4d7e84375a18: Pull complete da2c0f00d18a: Pull complete f5f79444c314: Pull complete 6843daded3de: Pull complete 4d31c6b31a1f: Verifying Checksum Pulling repository master.dev.local:50443/integral Error: image integral:trusty not found

        However, if I pull it first, I can run it then:

        me@worsktation:/$ sudo docker pull master.dev.local:50443/integral:trusty trusty: Pulling from integral

        4d31c6b31a1f: Pull complete Digest: sha256:46219cf7fe6e0375588a6238d3ac53c8bef4a8dca94d5f85949075b56bd9983e Status: Downloaded newer image for master.dev.local:50443/integral:trusty

        me@worsktation:/$ sudo docker images REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE master.dev.local:50443/integral trusty 4d31c6b31a1f 5 days ago 567.6 MB

        But unfortunatelly the 2 step run (pull and run) is not working inside a makefile, which is my goal.

        So why are we making directories for certs in /usr/local when docker’s installer is setting up shop under /etc/docker?

        Also, it’s not clear what local user is being used on the client machine.

        I have been struggling with Docker for a few weeks on and off. This guide really broke things wide open. Much appreciation for taking the time to produce such a great guide.

        Wasn’t able to run ssl configuration with nginx as desribed in tutorial:

        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;
        

        However was able to run it as follows:

        server {
          listen 443 ssl;
          server_name myregistrydomain.com;
        
          # SSL
          ssl on;
          ssl_certificate /etc/nginx/conf.d/domain.crt;
          ssl_certificate_key /etc/nginx/conf.d/domain.key;
        

        Could you help me resolve this: I can login in both server side and client side I can push and pull image from client side, but I can not push or pull image in server side (host machine)

        Even when I follow this instruction to: do this locally on the host machine so that we can use Docker from the Docker registry server itself

        Let's do this locally on the host machine so that we can use Docker from the Docker registry server itself:
        
        sudo mkdir /usr/local/share/ca-certificates/docker-dev-cert
        sudo cp devdockerCA.crt /usr/local/share/ca-certificates/docker-dev-cert
        sudo update-ca-certificates
        

        Here is the error

        root@dev:/docker-registry/nginx# docker push docker.xxx.xxx:5043/test-image
        The push refers to a repository [docker.xxx.xxx:5043/test-image]
        unable to ping registry endpoint https://docker.xxx.xxx:5043/v0/
        v2 ping attempt failed with error: Get https://docker.xxx.xxx:5043/v2/: x509: certificate signed by unknown authority
         v1 ping attempt failed with error: Get https://docker.xxx.xxx:5043/v1/_ping: x509: certificate signed by unknown authority
        

        Waiting for your help!!!

        If you are using docker-machine as client on MacOS X you probably will have trouble with self-signed certificate (certificate signed by unknown authority). I recommend to use insecure registry. Connect to your docker VM (I use default VM):

        docker-machine ssh default
        

        And add one line to /var/lib/boot2docker/profile

        sudo vi /var/lib/boot2docker/profile
        

        Add parameter to EXTRA_ARGS –insecure-registry https://your.registry.host I found the solution here. You should have something like this:

        EXTRA_ARGS='
        --label provider=virtualbox
        --insecure-registry https://your.registry.host
        
        '
        CACERT=/var/lib/boot2docker/ca.pem
        DOCKER_HOST='-H tcp://0.0.0.0:2376'
        DOCKER_STORAGE=aufs
        DOCKER_TLS=auto
        SERVERKEY=/var/lib/boot2docker/server-key.pem
        SERVERCERT=/var/lib/boot2docker/server.pem
        

        Then restart vm:

        docker-machine restart default
        

        That’s all.

        First Awesome tutorial!

        For those with SSL issues, i highly suggest you check out LetsEncrypt. I was able to add it and use my domain name, worked without issue, no private cert issues! If your domain is setup in DO, just add an A record to your domain ( IE registry … so you have registry.mydomain.io ) and point it to your registry server. Then use the name in the Lets Encrypt SSL generation. Also make sure to use the domain in the nginx config.

        I ran the first part of the instructions to install lets encrypt on the getting started page:

        $ git clone https://github.com/letsencrypt/letsencrypt
        $ cd letsencrypt
        $ ./letsencrypt-auto --help
        

        Then generated my cert:

        /opt/letsencrypt/letsencrypt-auto certonly --standalone -d registry.mydomain.io
        

        Run a dir on the new cert location so you can see the contents(it also tells you where to find these after they are generated):

        dir  /etc/letsencrypt/live/registry.mydomain.io/
        cert.pem  chain.pem  fullchain.pem  privkey.pem
        

        Change directory to the guide directory (/docker-registry/nginx), then copy the files to the folder( i also renamed them to match the config in the guide above in this command):

        cp /etc/letsencrypt/live/registry.mydomain.io/fullchain.pem domain.crt
        cp /etc/letsencrypt/live/registry.mydomain.io/privkey.pem domain.key
        

        Thats it!

        To renew the cert ( cause it expires in a few months ):

        /opt/letsencrypt/letsencrypt-auto renew -nvv --standalone -d registry.mydomain.io
        

        Then remember to copy the new files to the /docker-registry/nginx folder

        Would love to see this guide add a web interface to the registry!

        I set up private registry using self signed certificate but Getting Certificate signed by unknown authority error. I copied crt file to /etc/docker/certs.d/domain.com:port/ca.crt and also to /etc/pki/ca-trust/source/anchors/<domain.com>.crt. But no help. Any idea on it

        We just launched an alternative private Docker Registries Service that you can use in combination with DigitalOcean. The private-docker-registry.com has a very nice web user interface to mange user, teams and projects. It has also LDAP/AD/ integration. OAuth with Digital Ocean is planned in the near future. You can have an unlimited amount of private repositories on top.

        How does this fit in with using signed images?

        export DOCKER_CONTENT_TRUST=1
        

        Great tutorial, thanks for sharing your experience

        I am able to to execute curl http://subhajit:dutta@localhost:5043/v2/ OUTPUT : <html> <head><title>400 The plain HTTP request was sent to HTTPS port</title></head> <body bgcolor=“white”> <center><h1>400 Bad Request</h1></center> <center>The plain HTTP request was sent to HTTPS port</center> <hr><center>nginx/1.9.15</center> </body> </html> But when I am trying with hostname curl http://subhajit:dutta@hpeswlab.net:5043/v2/ its showing no output , Please help

        The above method is pretty outdated.

        Docker registry has ssl (+letsencrypt) and basic auth directly integrated now.

        Here’s a single service docker-compose.yml I created for my own needs, use as is or extend as required. (The configuration documentation on dockers site is really helpful)

        https://gist.github.com/lsl/a4637edf5553f27e475ae9191d832255

        Tested on ubuntu 16.04 LTS, docker 17.03.1-ce, docker-compose 1.13.0

        It also works on ubuntu 16.04! Thanks for the detailed tutorial

        If anyone is having issues with certificates and MAC OSX (self-signed or not), we found this:

        http://container-solutions.com/adding-self-signed-registry-certs-docker-mac/

        Hey there. I follow this tutorial and working great with linux client, but for windows client still cannot work. I am trying to docker push or pull from windows client and still got error : “Error response from daemon: Get https://myregistry.mydomain.com/v2/: x509: certificate signed by unknown authority”

        The question is:

        • In step 9 where i put the devdockerCA.crt in windows?
        • My docker toolbox version 18.03.0-ce running on windows 10 64 bit

        Thank you.

        In the setting up authentication - when create a user name using ngnix. can i create a user only for pull and not able to push in docker registry? If it is possible then what is the step?

        how to check images in docker private registry

        how to view available docker images in docker registry from client machine after successfully login

        how to create docker user only for pull using nginx from docker registry

        Try DigitalOcean for free

        Click below to sign up and get $200 of credit to try our products over 60 days!

        Sign up

        Join the Tech Talk
        Success! Thank you! Please check your email for further details.

        Please complete your information!

        Congratulations on unlocking the whale ambience easter egg!

        Click the whale button in the bottom left of your screen to toggle some ambient whale noises while you read.

        Thank you to the Glacier Bay National Park & Preserve and Merrick079 for the sounds behind this easter egg.

        Interested in whales, protecting them, and their connection to helping prevent climate change? We recommend checking out the Whale and Dolphin Conservation.

        Become a contributor for community

        Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

        DigitalOcean Documentation

        Full documentation for every DigitalOcean product.

        Resources for startups and SMBs

        The Wave has everything you need to know about building a business, from raising funding to marketing your product.

        Get our newsletter

        Stay up to date by signing up for DigitalOcean’s Infrastructure as a Newsletter.

        New accounts only. By submitting your email you agree to our Privacy Policy

        The developer cloud

        Scale up as you grow — whether you're running one virtual machine or ten thousand.

        Get started for free

        Sign up and get $200 in credit for your first 60 days with DigitalOcean.*

        *This promotional offer applies to new accounts only.