How to set up Nexus OSS 3 as a private Docker Registry

Steven Sheaves
6 min readDec 20, 2022

Introduction

Confused software engineers

If you are using a Nexus server to host your Docker images, you may need a way to push images to the server from your development. When setting up my docker registry in nexus, I faced challenges when trying to run the docker login command. My domain was https://sub.example.com and so when I created a docker repository, I copied the link which evaluated to https://sub.example.com/repository/<docker_repo_name> . When running the docker login command, however, the docker daemon kept getting a “404 not found” error from the nexus server. In order to get around this, you’ll need to work some reverse proxy magic.

TL;DR:

You’ll need to set up a reverse proxy that does a few things:

  1. Handle your SSL in your own way (I used Nginx + self-signed cert, then proxied to HTTP internally for the nexus instance to handle) for requests to your Nexus server.
  2. Create a second server block for docker requests that listens to a different SSL port. I’ll call it DOCKER_PORT (handle your SSL however you want)
  3. Proxy all GET requests that match exactly/v2 on your DOCKER_PORT to /repository/<docker_repo_name> to the port that your nexus instance is running on (this is what docker login will talk to)
  4. Proxy all GET requests that start with /v2 on your DOCKER_PORT to /repository/<docker_repo_name>/v2 to the HTTP port that you set up as a connector for your docker registry (this is what docker pullwill talk to)
  5. Proxy ever other request that starts with v2 on your DOCKER_PORT directly to the CONNECTOR_PORT that you’ll specify in the next step
  6. Set up a connector for your docker registry to accept HTTP requests on a port (CONNECTOR_PORT) that you specify (you could use HTTPS, but I did not since this is internal). This port can NOT be the same as your DOCKER_PORT. I set mine to 2055, because I am running Nexus on Docker, and have SSL port 2053 mapped to 2054 in the Docker container, then internally to 2055 where Nexus is listening for Docker requests.

In order to use this setup:

Login to docker using

docker login repo.example.com:<DOCKER_PORT>

Build your image

docker build <dockerfile_location> -t repo.example.com:<DOCKER_PORT>/<group>/<image_name>:<version/tag>

Push your image

docker push repo.example.com:<DOCKER_PORT>/<group>/<image_name>:<version/tag>

Skip to the bottom for an example of the final Nginx config that I used to handle my reverse proxy.

The problem

When I first tried to push an image to my docker repository, I ran the docker login command and got a very confusing “404 not found” error.

After much googling and digging, I finally realized that the docker login command was attempting to access https://repo.example.com/v2 which did NOT exist. As it turns out, even when I had provided the explicit URL https://sub.example.com/repository/<docker_repo_name> the docker daemon was stripping off the path, and attempting to login to the /v2 path.

The solution

Nginx reverse proxy to the rescue.
Since I was using Cloudflare for proxying, I already had Nginx set up for handling my SSL certificates, and proxying my request from my port 443:8443 where my Nexus OSS server was hosted.

Note: I am running my Nexus OSS server in Docker, so I already had Nginx set up to proxy port 443 to 8443, where my Nexus container was listening and had port 8443 mapped to 8081 inside the Docker container. This is important when it comes to the proxies because I had three ports to deal with, instead of 2.

In order to make this as simple as possible, I chose to use a different port for my Docker commands, and set up a separate port for docker altogether, but used the same SSL so that Cloudflare wouldn’t complain.

The resulting conf file looked like this:

server {
# Listens for requests from docker CLI
listen 2053 ssl http2;
listen [::]:2053 ssl http2;
server_name repo.example.com;

ssl_certificate /etc/ssl/certs/nexus-ssl.crt;
ssl_certificate_key /etc/ssl/private/nexus-ssl.key;
ssl_protocols TLSv1.2 TLSv1.1 TLSv1;

client_max_body_size 100M;


location / {
proxy_pass http://127.0.0.1:8443/repository/<docker_repo_name>;
access_log /var/log/nginx/docker.log;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_http_version 1.1;
proxy_cache_bypass @http_upgrade;
}
}

This will allow you to log in with Docker!
What happens here, is Nexus expects the docker login command to happen on the exact path of the /repository/<docker_repo_name> but NOT with the /v2 path.

But there was a catch.
Since I proxied /v2to /repository/<docker_repo_name>, I then had a conflict when actually pushing the image to the repository.

I was getting an error that says “Not a Docker request”

Turns out, this is caused by the request not coming in to /v2 , which is a bit confusing.

So, I had to find a way to proxy ONLY the GET request that EXACTLY matched the /v2 endpoint to the login location, and everything else has to include the /v2 path, but at the END of the /repository/<docker_repo_name> path, such that the requests go to /repository/<docker_repo_name>/v2

What I ended up doing is conditionally proxying to different ports, handling those ports separately, and proxying to the proper end location.

This is the final Nginx config file.

server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name repo.example.com;

ssl_certificate /etc/ssl/certs/nexus-ssl.crt;
ssl_certificate_key /etc/ssl/private/nexus-ssl.key;
ssl_protocols TLSv1.2 TLSv1.1 TLSv1;

access_log /var/log/nginx/nexus_proxy.log;
client_max_body_size 100M;

location / {
proxy_pass http://127.0.0.1:8443/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}


server {
# Listens for requests from docker CLI
listen 2053 ssl http2;
listen [::]:2053 ssl http2;
server_name repo.example.com;

ssl_certificate /etc/ssl/certs/nexus-ssl.crt;
ssl_certificate_key /etc/ssl/private/nexus-ssl.key;
ssl_protocols TLSv1.2 TLSv1.1 TLSv1;

client_max_body_size 100M;


location / {
access_log /var/log/nginx/docker.log;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

if ($request_method = GET) {
proxy_pass http://127.0.0.1:2000;
}

if ($request_method !~* GET) {
proxy_pass http://127.0.0.1:2001;
}
}
}

server {
# Handles GET requests
listen 2000;
listen [::]:2000;
server_name repo.example.com;
access_log /var/log/nginx/docker_get.log;


location ~* ^/v2/$ {
# docker login command uses /v2 GET request
proxy_pass http://127.0.0.1:2002;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}

location /v2 {
# docker pull command uses GET requests on endpoints that start with /v2
proxy_pass http://127.0.0.1:8443/repository/<docker_repo_name>/v2;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}

server {
listen 2001;
listen [::]:2001;
server_name repo.example.com;
access_log /var/log/nginx/docker_storage.log;

client_max_body_size 100M;


location /v2 {
proxy_pass http://127.0.0.1:2054;
proxy_http_version 1.1;
proxy_cache_bypass @http_upgrade;
}
}

server {
# Handles docker login
listen 2002;
listen [::]:2002;
server_name repo.example.com;
access_log /var/log/nginx/docker_login.log;

client_max_body_size 100M;


location / {
proxy_pass http://127.0.0.1:8443/repository/<docker_repo_name>/v2/;
proxy_http_version 1.1;
proxy_cache_bypass @http_upgrade;
}
}

And voila! You’re able to push docker images to Nexus OSS!

In order to use this setup:

Login to docker using

docker login repo.example.com:<DOCKER_PORT>

Build your image

docker build <dockerfile_location> -t repo.example.com:<DOCKER_PORT>/<group>/<image_name>:<version/tag>

Push your image

docker push repo.example.com:<DOCKER_PORT>/<group>/<image_name>:<version/tag>

Give this solution a try and let us know if it works for you! Setting up Nexus OSS as a Docker registry doesn’t have to be a headache — with this simple fix, you’ll be up and running in no time.

Find me on LinkedIn
Check out my startup GoalShark, which is the whole reason I’m setting up a Nexus OSS Server, to begin with!
Find me on GitHub
Find me on YouTube

--

--