Setting up a private Docker registry behind Apache with HTTP basic authentication and authorization

Written on June 25, 2015

While there's an official guide for deploying Docker registry 2.0, it doesn't fully describe how to run Docker registry 2.0 in production. We couldn't find a guide on which settings to change and setting up basic authentication had a few hurdles, so we show in this post how we made our setup work.

For authentication, the registry supports a central token service. While there is an early implementation for such a token service called Portus, we don't need fine-grained authorization and don't want to run and maintain another service if we don't have to. Thus, we configure Apache to do authentication and authorization. Authorization works by allowing HTTP methods that may change state on the server only for a certain user (our CI).

Configuring and running the registry

The docker registry 2.0 can be configured using a YAML-file and environment variables. For us, it was easier to add a YAML-file into an image based on the registry:2.0 image. We used the following simple Dockerfile.

FROM registry:2.0

COPY docker/config.yml $DISTRIBUTION_DIR/cmd/registry/config.yml

Our docker/config.yml looks like this:

version: 0.1
log:
  level: info
  fields:
    service: registry
    environment: production
storage:
    cache:
        layerinfo: inmemory
    filesystem:
        rootdirectory: /var/lib/registry
    maintenance:
        uploadpurging:
            enabled: false
http:
    addr: :5000
    secret: [SETME]
redis:
  addr: localhost:6379
  pool:
    maxidle: 16
    maxactive: 64
    idletimeout: 300s
  dialtimeout: 10ms
  readtimeout: 10ms
  writetimeout: 10ms

You'll have to at least replace the http.secret value or set this secret via the environment variable HTTP_SECRET.

After building an image (docker build -t our-registry .), we can run it.

docker run -p 127.0.0.1:5000:5000 \
           -v /var/lib/docker-registry:/var/lib/registry \
           --restart=always \
           --name=registry \
           --detach \
           --log-driver=syslog \
           our-registry
  • -p 127.0.0.1:5000:5000 Expose port 5000 (where the registry HTTP server is running) to the host on 127.0.0.1, so we can later direct the Apache reverse proxy to a static IP.
  • -v /var/lib/docker-registry:/var/lib/registry Mount /var/lib/docker-registry from the host, so registry data persists when we create a new container. You can of course use a data container instead if you want.
  • --restart=always Make Docker restart the container in case it unexpectedly stops or the machine reboots.
  • --log-driver=syslog We log everything to syslog and store all messages centrally, this sends the registry's output there. If you don't do this, make sure to do log rotation - the registry in our configuration does pretty verbose logging.

Configuring Apache 2 for authentication and authorization

Since there only exist apache2 configurations for the older v1 registry we had to start from scratch.

What we wanted to have from the beginning was the limitation that only our CI is able to push new images to the registry so all other are only allowed to pull them. We got there using apache's limit directive. Of course we're using the registry over SSL/TLS. Here is what it looks like.

<VirtualHost *:443>
  ServerAdmin admin@example.com
  ServerName registry.example.com

  SSLEngine on
  SSLCertificateFile    /etc/ssl/certs/example_com.crt
  SSLCertificateKeyFile /etc/ssl/private/example_com.key
  SSLCertificateChainFile /etc/ssl/certs/example_com_intermediates.crt
  Header add Strict-Transport-Security "max-age=31536000"
  SSLProtocol All -SSLv2 -SSLv3

  ProxyPass / http://localhost:5000/
  ProxyPassReverse / http://localhost:5000/

  ProxyRequests Off
  ProxyPreserveHost on

  Header always set Docker-Distribution-Api-Version "registry/2.0"
  RequestHeader set X-Forwarded-Proto "https"
  RequestHeader set Authorization ""

  <Location />
    Order allow,deny
    Allow from all
    AuthType Basic
    AuthName "Docker Registry"
    AuthUserFile /var/.registrypwd
    AuthGroupFile /dev/null
    Require user jenkins
    <Limit GET HEAD>
      # This limits "any" user to GET/HEAD requests for pulling images
      Require valid-user
    </Limit>
  </Location>

  ErrorLog ${APACHE_LOG_DIR}/registry-error.log
  LogLevel warn
  CustomLog ${APACHE_LOG_DIR}/registry-access.log
</VirtualHost>

Most of the configuration is pretty standard, these are the options we added specifically for the registry:

  • Header always set Docker-Distribution-Api-Version "registry/2.0" The Docker client checks whether the endpoint it attempts to reach is a v2 registry and expects this header. The registry itself returns this header, but Apache only forwards requests after authentication and the docker client expects the header before authentication.
  • RequestHeader set Authorization "" When authentication is enabled, the docker client sends an HTTP basic auth header. Stripping the header here ensures the docker registry doesn't
  • <Limit GET HEAD> In the outer <Location> block, we only allow our jenkins user access. We want to allow everyone else to pull images, but not to push them. Limiting the HTTP methods to GET and HEAD does that.

You'll of course also have to create the .registrypwd file with the htpasswd command. Instead of the file-based authentication you can also easily use LDAP.

By Michael Frister and Martin Seener