Docker Repository Reverse Proxy Strategies

Introduction

The Docker client has strict requirements about how it can retrieve content from a repository (i.e., a registry). These requirements mainly center around the path at which it expects everything to be hosted.

While it is possible to tell the Docker client to use a chosen host from which to retrieve (or to which to upload) images, it is not possible to tell it to use an arbitrary base path where images are stored in a registry. 

To further explain, the Docker client is given a registry to contact by specifying only the hostname + port. It's also given a specific path to an image in that registry. So, for example, it would be given "example:443/some/custom/image" to specify an image. You are not able to specify a registry application path.

Nexus Repository exposes its Docker registries with a repository path of /repository/<repo_name>/ and, by default, and application context path of /.

So, a full Docker image in the repository "docker-hosted" might be accessible at full URL example:443/nexus3/repository/docker-hosted/some/custom/image, which can be broken down as follows:

  • example.com = host name
  • 443 = port
  • /nexus3 = application context path
  • /repository/docker-hosted = base registry path
  • /some/custom/image = specific image path in the registry

There is no way to give the Docker client the application context path or base registry path. Docker needs the registry exposed at the root of the host + port that it is accessing.

This is important because Nexus Repository uses request paths to separate content between different repositories. There are two potential ways to overcome this Docker limitation: using port connectors or using a reverse proxy. This page describes how to use a reverse proxy to access Docker registries hosted in Nexus Repository.

Note that SSL is required for Docker to work properly.

Intended Use Cases

Using a reverse proxy in front of Nexus Repository for Docker repositories is an option to consider for the following use cases:

  • Multiple connectors inside of Eclipse Jetty or Nexus Repository would cause performance issues
    • If more than 20 connectors are needed, you must use a reverse proxy
  • You need to limit the number of open ports for infrastructure or security reasons
  • Managing secure connectors/ports/hosts inside an external reverse proxy aligns with organizational goals and infrastructure

Implementation Overview

Once implemented as described in this topic, Nexus Repository listens at a non-https connector such as the default 8081.

You can add Docker repositories into Nexus Repository as normal, but do not configure them with any connector port values. 

In the simple example, the following Docker repository hierarchy would exist inside Nexus per project team:

  • docker-group-project
    • docker-hosted-project
    • docker-hub

Based on the host name or port of the request to a reverse proxy, the reverse proxy decides where to reverse proxy the request into Nexus Repository.

Port Mapping Example

In the following Docker push port mapping example, the reverse proxy would direct Docker push commands received at https://project.example.com:8086 to http://nexus.example.com:8081/repository/docker-hosted-project.

docker login project.example.com:8086
docker push project.example.com:8086

In the following Docker pull port mapping example, the reverse proxy would direct Docker pull commands received at https://project.example.com:8087 to http://nexus.example.com:8081/repository/docker-group-project.

docker login project.example.com:8087
docker pull project.example.com:8087

The following example Apache HTTP Server configuration port maps https://project.example.com:8087 to http://nexus.example.com:8081/repository/docker-repo.

Listen 8087 https

<VirtualHost *:8087>
  ServerName project.example.com
  ServerAdmin admin@example.com

  AllowEncodedSlashes NoDecode
  ProxyRequests Off
  ProxyPreserveHost On

  SSLEngine ON
  SSLCertificateFile "/path/to/ssl/server.crt"
  SSLCertificateKeyFile "/path/to/ssl/server.key"

  ProxyTimeout 300
  ProxyPass / http://nexus.example.com:8081/repository/docker-repo/ nocanon
  ProxyPassReverse / http://nexus.example.com:8081/repository/docker-repo/
  RequestHeader set X-Forwarded-Proto "https"
</VirtualHost>

Host Mapping Example

A wildcard TLS certificate is needed for the host name mapping scenario if you intend to have more than one project team specific host name.

In the following Docker push host mapping example, the reverse proxy would direct Docker push commands received at https://project-push.example.com to http://nexus.example.com:8081/repository/docker-hosted-project.

docker login project-push.example.com
docker push project-push.example.com

In the following Docker pull host mapping example, the reverse proxy would direct Docker pull commands recieved at https://project.example.com to http://nexus.example.com:8081/repository/docker-group-project.

docker login project.example.com
docker pull project.example.com

The following example Nginx configuration host maps https://project.example.com to http://nexus.example.com:8081/repository/docker-repo.

This Nginx host mapping example uses the same server for both Docker and regular Nexus Repository requests; it also uses location directives to proxy /v2 or /v1 requests to the Docker repository. Docker automatically adds these v1/v2 paths to the requests and does not require users to add them directly to the Docker commands.

http {

  proxy_send_timeout 120;
  proxy_read_timeout 300;
  proxy_buffering    off;
  proxy_request_buffering off;
  keepalive_timeout  5 5;
  tcp_nodelay        on;

  server {
   listen 443 ssl;
   server_name project.example.com

   ssl on; 
   ssl_certificate /path/to/ssl/server.crt;
   ssl_certificate_key /path/to/ssl/server.key;

   # Docker /v2 and /v1 (for search) requests
   location /v2 {
     proxy_set_header Host $host:$server_port;
     proxy_set_header X-Real-IP $remote_addr;
     proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
     proxy_set_header X-Forwarded-Proto "https";
     proxy_pass http://nexus.example.com:8081/repository/docker-repo/$request_uri;
   }
   location /v1 {
     proxy_set_header Host $host:$server_port;
     proxy_set_header X-Real-IP $remote_addr;
     proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
     proxy_set_header X-Forwarded-Proto "https";
     proxy_pass http://nexus.example.com:8081/repository/docker-repo/$request_uri;
  }

   # Regular Nexus requests
   location / {
     proxy_set_header Host $host:$server_port;
     proxy_set_header X-Real-IP $remote_addr;
     proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
     proxy_set_header X-Forwarded-Proto "https";
     proxy_pass http://nexus.example.com:8081;
   }
 }}