Once upon a time1, the company I work for decided to migrate all Docker images from a hosted registry to a self-hosted one. The main requirement, in line with the Principle of Least Privilege2, was to restrict service accounts to access only their designated images. This ensures that every project could only access its own images.

Since we were already using Nexus 3 as our private NPM registry, we decided to configure it for this use case as well. I had also configured something similar before, using the raw repository type. In the mentioned case, we published runnable JARs (builds per customer) to Nexus and allowed end users to download them with dedicated accounts4.

Setting up a new registry was straightforward, but restricting access to specific images required more effort - in fact a deeper understanding of Content Selectors5.

Steps and assumptions

The steps below assume that we have admin access to Nexus and a working Docker registry:

  1. Configure Content Selectors for login and image access.
  2. Create privileges based on the created Content Selectors.
  3. Create the role with new privileges and apply them to the user.

And that’s it! :-)

1. Configuring Content Selectors for Docker Login and accessing image

To achieve this, we need to configure Content Selectors5 that will allow us to:

  • use the docker login command.
  • pull only the selected images (any tag).

To allow logging into the repo we will need the following content selector - it matches the path that is used by the docker login command:

path == "/v2/"

So the content selector could look like this:

Docker Login - Content Selector in Nexus Sonatype Repository OSS

Apart from allowing us to log in, we need to allow access to selected images. To allow all images in the following path we will use =^6 to match all paths that start. Assuming that our image is sonatype/nexus3 then we need to create a selector:

path =^ "/v2/sonatype/nexus3"

Which translates to the following Content Selector:

Access to sonatype/nexus3 - Content Selector in Nexus Sonatype Repository OSS

We could merge both into the one with the or operator. Keeping them separate allows the first selector (for login) to be reused multiple times.

2. Creating privileges

As a next step, we need to create new privileges - each for the newly created content selector. Both privileges should be configured for the read action, as we only need read-only access.

In the screenshots below I select my Docker registry with the name docker, but nothing stops us from selecting here (All docker repositories).

Access to docker login - Privilege in Nexus Sonatype Repository OSS

Access to sonatype/nexus - Privilege in Nexus Sonatype Repository OSS

After that, we can go to the last step which is… creating a role and assigning it to our service account!

3. Creating the role and assigning it to the user

Now we will create the role (with type Nexus role) and we will apply previously created privileges:

Role creation in Sonatype Nexus Repository OSS - contains two privileges created in previous steps

Then, cherry on the top, we will add that role to our service account:

Added role with name ‘docker-access-sonatype-nexus3’ to service account with name ‘k8s-sa’

That’s all. Now we can grab the login and password for our service account, log in and pull the images. Which is perfect for pulling images from the registry on the Kubernetes cluster. :-)

End notes

In a few steps, we have configured limited access to selected Docker images in our self-hosted registry. Even if that solution was nothing extraordinary, it allowed us to migrate from the hosted repository and have more control over image access.

Worth noting that it was a prerequisite to set up Nexus as a proxy in front of our GitLab instance - but this is the story for another time.


  1. 6 months ago, but in IT it is like decades, riiight? ↩︎

  2. Principe Of Least Privilege means that the access shall be as limited as can be. ↩︎

  3. Yes, Sonatype Nexus Repository OSS, as it is free to use and allows to store maven and NPM artifacts! ↩︎

  4. raw repository as we do not want to expose the Maven repo structure. In the end, we dropped that way of distribution as we moved to providing Docker images. ↩︎

  5. The documentation for Content Selectors can be found here ↩︎ ↩︎

  6. That operator =^ looks like an emoticon, isn’t it? ↩︎