The Docker team has made it easy for us to host our own private docker registry by providing us with an Open Source, Python, web application for doing so. The web application also exists on the Docker hub as a single Docker image that we can execute to have our registry up and running as a Docker container. The running container provides us with a registry we may push and pull from, but it leaves it to us to secure the registry on our server via SSL, and optionally, basic authentication.

Securing the registry is important, as the folks at Docker have made clear. See the recent updates strongly encouraging the use of SSL when interacting with private registries. There are plenty of options for setting up the registry and protecting it. The post, How To Set Up a Private Docker Registry on Ubuntu 14.04, does a good job of explaining how to do so without the use of containers. Here, I'll show how we can set up our own secure registry leveraging Docker containers. The basic principle is this: We run the registry container as we normally would, then fire up a separate nginx container to provide the SSL and basic authentication. The nginx container will proxy the HTTPS requests internally over HTTP to the linked registry container.


  • A server accessible by the Docker instances that wish to push and pull images from it. This could be a public server, a server within your organizations private network, etc.
  • Docker needs to be installed and running on the above server.

Generate the basic authentication credentials

Use the htpasswd command to create a new .htpasswd file with the desired credentials. I had to install the apache2-utils package on Debian. Save the created file, we'll need it later.

$ htpasswd -c ~/my-registry.htpasswd USERNAME
$ # then enter password at prompt

Generate the self-signed SSL certificate

Previously at CenturyLink Labs we had created an image to facilitate creating self-signed certificates for Panamax. We can leverage that image here. This will create the private key, certificate signing request, and certificate inside the ~/certs directory. It doesn't really matter where we stash these at the moment, just keep the generated files handy, as we'll need the certificate and private key later.

$ mkdir ~/certs
# the value for COMMAN_NAME should be updated to match the public CNAME or IP address of the server hosting the registry.
$ docker run --rm  -e COMMON_NAME=66.555.98.212 -e KEY_NAME=my-registry -v ~/certs:/certs centurylink/openssl

Note: Generating the SSL certificate went pretty quick here, but I'd encourage the reader to investigate this a bit more on their own. It's also possible, and generally preferred, to use a CA signed certificate. I feel that's beyond the scope of this post, so I'll leave it to the reader to decide how to handle this.

Run it

Run the registry container, being careful not to bind the port to the host. We only want this container accessible via the docker link.

$ docker run -d --expose=5000 --name=registry registry

# check that the container is running:
$ docker ps | grep registry
803c77e2f328        registry:latest                          "docker-registry"      19 minutes ago      Up 19 minutes       5000/tcp                                  registry

With the registry container running, spin up the proxy container, passing in our public certificate, private key, and .htpasswd file. Additionally, we need to map port 8080 to the host (feel free to map 8080 to any port you like on the host) and supply the PUBLIC_IP_ADDR environment variable.

# PUBLIC_IP_ADDR can be an ip or CNAME, make sure to change the below value to your own.
$ docker run -d -p 8080:8080 -e PUBLIC_IP_ADDR=66.555.98.212 --link registry:registry -v ~/certs/my-registry.crt:/etc/ssl/certs/docker-registry -v ~/certs/my-registry.key:/etc/ssl/private/docker-registry -v ~/my-registry.htpasswd:/etc/nginx/docker-registry.htpasswd centurylinklabs/nginx-ssl-proxy

Note: There was more handwaving in this step. We buried a bit of the work inside the centurylinklabs/nginx-ssl-proxy image. The source is located on GitHub.

Test it out!

If all went well you should be able to copy your public certificate to another machine and curl the endpoint.

$ curl -u user:password --cacert my-registry.crt https://66.555.98.212:8080
"\"docker-registry server\""
$ curl --cacert my-registry.crt https://66.555.98.212:8080/_ping #ping should work without auth
$ curl -k https://66.555.98.212:8080/_ping #using -k and hitting the _ping endpoint will bypass the ssl verification step

See the post How to use your own registry for information on working with the private registries.


Securing the registry is important. The Docker team made it pretty easy to host your own private docker registry by providing Open Source, Python, and web applications for doing that. Don't have an account on CenturyLink Cloud? No problem. If you don’t have a CenturyLink Cloud account yet, head over to our website and activate an account.

Sign up for our Developer-focused newsletter CODE. Designed hands-on by developers, for developers. Keep up to date on topics of interest: tutorials, tips and tricks, and community building events.