Running Secured Docker Registry 2.0

The new Docker Registry 2.0 was released on April 16th, 2015. It was completely rewritten in Go with added support for the new Docker Registry HTTP API V2 (thus only working with Docker 1.6+), promising to provide faster and more secure distribution of images. If you work with Docker and for some reason decided not to use the public Docker Hub, a private Docker Registry is an essential part of your architecture. But even if you don’t have private images, you will likely need to use your own registry in production/testing for efficiency.

The default installation, however, runs without encryption and authentication. I was wondering what’s involved in securing it. There is an official tutorial on how to configure TLS on a registry server. TLS/SSL is absolutely necessary for any secure setup, but I also wanted to enable an authentication mechanism. The Configuration Reference document describes two authentication options supported by Docker Registry itself: so-called silly and token solutions. The silly one is apparently only useful for very limited development use-cases. The token solution seems to be more serious, but because of the lack of documentation (at the time of writing), I decided to find an alternative approach to secure it. In this article I’m going to show you how to set up the Docker Registry 2.0 with username/password authentication and SSL using the official Docker Registry image and a custom configured nginx as a proxy server.

Note:

Docker daemon considers any private registry secure only if it uses transport layer security, a copy of its CA certificate is placed on the Docker host at /etc/docker/certs.d/<ip>:<port>/ca.crt and Docker is able to verify the certificate validity. In other cases (including usage of self-signed certificates), you need to run the Docker daemon with --insecure-registry flag.

First, run the registry:

docker run -v $(pwd)/data:/tmp/registry-dev --name docker-registry registry:2.0

I gave it a name, so we can refer to it from the nginx configuration. Because it’s nice to have stateless containers, I attached a volume where registry stores its data.

Because we want our registry to be accessible only by people who know the password, let’s create a .htpasswd file. You can do it like this:  htpasswd -c .htpasswd exampleuser

The last bits are writing the nginx configuration and finally running the proxy. The nginx config file might look like this:

We need to use always parameter of add_header directive, introduced only recently in nginx 1.7.5. The reason is that nginx doesn’t send headers with auth_basic requests by default. Previously, you’d have to use nginx-extras package that includes HttpHeadersMore plugin and use that.

Looking at the rest of the nginx configuration; it’s SSL only proxy, so we need to attach the certificates to the container to /etc/nginx/ssl/docker-registry.crt and /etc/nginx/ssl/docker-registry.key respectively. There is auth_basic enabled, for which we need to attach /etc/nginx/.htpasswd file, which we just generated. We use name of the already running registry:2.0 container in the proxy_pass directive, together with port 5000, exposed from the container by default.

Now we run the proxy container. You can use Docker Registry Proxy image, that we created for your convenience. It’s derived from nginx:1.7 and applies the configuration described above. You only need to provide it with REGISTRY_HOST and REGISTRY_PORT variables pointing to the registry container and SERVER_NAME variable that stands for the nginx  server_name  directive. A directory with SSL certificates must be mounted to the container as well as our .htpasswd file. Expose HTTPS port 443 to the host and add a link to our already running docker-registry container:

Let’s verify that our Registry works properly.

You should see a successful push to your private Docker Registry, secured with SSL and basic http authentication. Although this setup seems to work, use it at your own risk and please let us know if you spot any issues!

The following two tabs change content below.

Jaroslav Holub

Latest posts by Jaroslav Holub (see all)

23 Comments

  1. HI,

    I like your blog and I’m in the process of updating our documentation with a basic auth how-to. Would you mind if I used your Nginx image? I’m happy to credit your blog in our docs. What do you think?

    Mary

  2. The problem i saw with the proxy solution is that it doesn’t let you do fine-grained access control – like, allow push to some users and pull to others.

    Since 2.0 actually supports tokens, the only missing piece is the server to generate them.
    So I wrote one and just pushed it to github: https://github.com/cesanta/docker_auth

  3. Thanks for the image! I am trying to use it but seem to struggle with the certificate.. I have installed everything generated a .crt / key file and placed the .crt file as ca.crt in my docker client in the correct place. When i try to login i get the error:

    FATA[0004] Error response from daemon: no successful auth challenge for https://ec2-54-154-172-179.eu-west-1.compute.amazonaws.com:443/v2/ – errors: [Get https://ec2-54-154-172-179.eu-west-1.compute.amazonaws.com:443/v2/: x509: certificate signed by unknown authority]

    Any hints where i can look to get this going?

  4. I am having the same issue as reported by Marco. I can test docker login locally to be successful. But when I attempt to push images (client docker version 1.6) to v2 private registry (self signed cert), i get the error

    Error response from daemon: no successful auth challenge for https:///v2/ – errors: [Get https:///v2/: x509: certificate signed by unknown authority]
    I get a 401 unauthorized

    [29/Jun/2015:06:25:47 +0000] “GET /v2/ HTTP/1.1” 401 195 “-” “docker/1.6.0 go/go1.4.2 kernel/3.10.0-123.8.1.el7.x86_64 os/linux arch/amd64” “-”

    Testing docker login locally on the v2 registry server

    docker login –username=”” –password=”” –email=”” https://
    WARNING: login credentials saved in /root/.docker/config.json
    Login Succeeded

  5. I am having the same issue as reported by Marco. I can test docker login locally to be successful. But when I attempt to push images (client docker version 1.6) to v2 private registry (self signed cert), i get the error

    Error response from daemon: no successful auth challenge for https:// /v2/ – errors: [Get https:// /v2/: x509: certificate signed by unknown authority]
    I get a 401 unauthorized

    [29/Jun/2015:06:25:47 +0000] “GET /v2/ HTTP/1.1” 401 195 “-” “docker/1.6.0 go/go1.4.2 kernel/3.10.0-123.8.1.el7.x86_64 os/linux arch/amd64” “-”

    Testing docker login locally on the v2 registry server

    docker login –username=”” –password=”” –email=”” https://
    WARNING: login credentials saved in /root/.docker/config.json
    Login Succeeded

    • Hi Satish, can you please provide more information about your setup? We can’t reproduce the issue you described.

    • Hello Jaroslav,
      sorry for the late reply, I will provide detailed info on my setup this evening (Pacific timezone) as I am traveling.

      Thanks
      Satish

    • Sorry for the such late response, it was a PICNIC (problem in chair not in computer) syndrome i.e. user error, I failed to read this part of the verbiage

      “Docker daemon considers any private registry secure only if it uses transport layer security, a copy of its CA certificate is placed on the Docker host at /etc/docker/certs.d/:/ca.crt and Docker is able to verify the certificate validity. In other cases (including usage of self-signed certificates), you need to run the Docker daemon with –insecure-registry flag.”

      Thanks for all the help.

      -Satish

  6. Thanks for this blog!
    I managed to create a registry and it is working perfectly, using HTTPs.
    You must have a genuine paid certificate though, self signed will not work.
    Also if the certificate domain is *.example.com than your registry must be in this domain, i.e something like registry.example.com.
    This subdomain must be configured also in your domain so it will point to the right IP/host.

    • WIth self-signed certs there seems to be a problem with basic_auth on Nginx specifically with brcypt encryption. I hit the same error as I had previously posted with respec to docker login and also as reported by Marco.

      Essentially, with self signed cert
      1. docker login works on the registry – no issues here
      2. when you attempt to perform a docker login from a remote host (in order to auth and then push images) – this fails

      Example:

      docker login -u -p -e .cloudapp.net
      FATA[0000] Error response from daemon: no successful auth challenge for https://.cloudapp.net/v2/ – errors: [Get https://.cloudapp.net/v2/: x509: certificate signed by unknown authority]

      OS: CentOS 7

      Here’s running the nginx proxy

      #!/bin/bash

      docker run -d -p 443:443 \
      -e REGISTRY_HOST=”registry_v2″ \
      -e REGISTRY_PORT=”5000″ \
      -e SERVER_NAME=”.cloudapp.net” \
      –link registry_v2:registry_v2 \
      -v /data/basic_auth/.htpasswd:/etc/nginx/.htpasswd:ro \
      -v /data/certs:/etc/nginx/ssl:ro \
      –restart=always –name nginx containersol/docker-registry-proxy

      systemctl status docker.service

      docker.service – Docker Application Container Engine
      Loaded: loaded (/usr/lib/systemd/system/docker.service; disabled)
      Active: active (running) since Sun 2015-08-09 22:48:21 CDT; 1h 52min ago
      Docs: https://docs.docker.com
      Main PID: 54132 (docker)
      CGroup: /system.slice/docker.service
      ├─54132 /usr/bin/docker -d –insecure-registry .cloudapp.net:443 -H fd://
      ├─57775 docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 5000 -container-ip 172.17.0.1 -container-port 5000
      └─58365 docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 443 -container-ip 172.17.0.5 -container-port 443

    • Sorry for the spam. So finally I figured it out. Secure v2 docker registry does very much work with a self-signed cert. Don’t forget to copy the docker registry cert to /etc/pki/ca-trust/source/anchors/ (if OS = RedHat based) and then make sure OS has access to the certs. The expected behavior would be to add the registry CA to the list of trusted roots along with the system roots. Issue the following command

      $ update-ca-trust enable;update-ca-trust extract

      See: https://github.com/docker/docker/issues/12756

      and yes, don’t forget to restart the docker daemon.

      Thanks ,
      Satish

    • Hi, first of all thx for the blog and the helpful comments. I managed to setup a private registry server with self signed certs, but I can’t search the registry for images. Pulling and push works fine and the images are at the right place. However, I get an empty response { } (json file is empty?) via curl … myreg.server/v2/ and for all other attempts “404 page not found” (same for docker search …). Only curl … myreg.server/v1/_ping tells me “V2 registry”. I tried the option -e SEARCH_BACKEND=sqlalchemy but it did not help. How do I manage or setup an index or do I miss something else. Any help is appreciated.

  7. should be

    Otherwise an extra : gets added to the output:

  8. Thanks for very good tech -note !

    I have one question – what is goal of SSL certs etc if everybody without SSL/CERT/KEY can login to my repo and push/pull images?

    Is it possible to give access only these computers which belongs to my Public Key Infractructure chain ? (it means all computers which have signed crt and keys by my private CA) ?

  9. just one hint for self signed certificates with Ubuntu docker client :

    on client:
    cp OUR_SELF_SIGNED_ROOT_CA.pem /usr/local/share/ca-certificates/ca.crt
    update-ca-certificates
    restart docker daemon
    docker login https://REGISTRY_IP

Leave a Reply

Your email address will not be published. Required fields are marked *