Preconfigured Nexus Repository Manager (NXRM) Docker container
Tags: docker, nexus, proxy, cache
Published: 2021-06-10 10:48:31

**TL;DR**: Link To Project @ [GitHub - unfor19/nexus-ops](https://github.com/unfor19/nexus-ops)

Recently, I've been looking for a way to avoid hitting [DockerHub rate limits](https://docs.docker.com/docker-hub/download-rate-limit/). One of the solutions that I came up with was deploying [Nexus Repository Manager OSS](https://www.sonatype.com/products/repository-oss) (NXRM) as part of my CI/CD environment, so the CI/CD runners hit NXRM instead of [DockerHub](http://dockerhub.com/) when attempting to pull public Docker images.

As always, I Googled a lot on how to get started with NXRM, and it seemed like it's best just to run it locally and see how it goes.

Ladies and gents, I give you my hands-on experience with provisioning a preconfigured NXRM Docker container.

## The Goal

NXRM Docker container that is preconfigured with [DockerHub](http://dockerhub.com/) and [AWS ECR Public Gallery](https://gallery.ecr.aws/). Here's what I found in Google:

- [Automated Setup of Sonatype Nexus Repository Manager
](https://blog.mimacom.com/automate-nexus/)
- [NXRM Integration API Scripting Examples](https://github.com/sonatype-nexus-community/nexus-scripting-examples/tree/master/complex-script)
- [Nexus Community - Automated provisioning of nexus](https://community.sonatype.com/t/automated-provisioning-of-nexus/2544)

## My Issue With Existing Solutions

I want to run the `init-script` immediately when NXRM is ready and part of the NXRM startup. I don't want to execute any script manually on my machine; everything should be done as part of NXRM's startup process. Most existing solutions describe running something on your machine that will communicate with NXRM; I'm looking for something different.

## Desired Outcome

Let's break it into a few bullet points that will describe the desired output.

- The `init-script` is readable and easy to maintain.
- Restarting the NXRM container should be idempotent; the `init-script` should not attempt to create existing resources.
- The `init-script` should be written in a popular scripting language, like [Bash](https://www.gnu.org/software/bash/) (so I chose Bash).
- Automated actions should be done using [NXRM REST API](https://help.sonatype.com/repomanager3/rest-and-integration-api), to support adding more features easily. Since I chose Bash, I'm using [curl](https://curl.se/) to make HTTP requests.

## Designing The Init Script

The desired outcome is clear; now, I need to declare the actions that the `init-script` will perform when starting the NXRM container. Here's the list of actions (in order):

1. Changes the [initial random password](https://help.sonatype.com/repomanager3/system-configuration/access-control/users) that is in `/nexus-data/admin.password` to `admin`
2. Enables [anonymous access](https://help.sonatype.com/repomanager3/system-configuration/user-authentication/anonymous-access) - allows anonymous users to access `localhost:8081` with `READ` permissions
3. Adds [Docker Bearer Token Realm](https://help.sonatype.com/repomanager3/formats/docker-registry/docker-authentication#DockerAuthentication-AuthenticatedAccesstoDockerRepositories) - allows anonymous pulls from local Nexus registry `localhost:8081`
4. Creates two Docker repository of type [proxy](https://help.sonatype.com/repomanager3/formats/docker-registry/proxy-repository-for-docker)
   1. `docker-hub` - DockerHub
   2. `docker-ecrpublic` - AWS ECR Public
5. Creates a Docker repository of type [group](https://help.sonatype.com/repomanager3/formats/docker-registry/grouping-docker-repositories)
   1. `docker-group` - The above Docker repositories are members of this Docker group

## Creating A Custom Docker Image

I've created a Docker image based on [NXRM official Docker image](https://hub.docker.com/r/sonatype/nexus3/).

Here's is how the Dockerfile looks like:

```dockerfile

ARG NEXUS_VERSION="3.30.1"

FROM sonatype/nexus3:${NEXUS_VERSION} as app
USER root
RUN curl -L -o /usr/local/bin/jq https://github.com/stedolan/jq/releases/download/jq-1.6/jq-linux64 && \
    chmod +x /usr/local/bin/jq

USER nexus
ENV NEXUS_DATA_PATH="/nexus-data" \
    NEXUS_ADMIN_USERNAME="admin" \
    NEXUS_ADMIN_PASSWORD="admin" \
    NEXUS_API_PATH="service/rest/v1" \
    NEXUS_BASE_PATH="http://localhost:8081" \
    NEXUS_OPS_VERBOSE="false"
WORKDIR /"${NEXUS_DATA_PATH}/nexus-ops/"
COPY provision/ .
CMD /nexus-data/nexus-ops/entrypoint.sh
```

> **Note**: I added [jq](https://stedolan.github.io/jq/download/) to the mix because I intend to use more APIs, and I'll need to analyze some JSON data. I find it best to analyze JSON data with `jq` when using `Bash`.

As you can see from the [Dockerfile](https://github.com/unfor19/nexus-ops/blob/master/Dockerfile) above, I'm copying the directory [provision/](https://github.com/unfor19/nexus-ops/tree/master/provision) to the NXRM image, and instead of running [/opt/sonatype/start-nexus-repository-manager.sh](https://github.com/sonatype/docker-nexus3/blob/master/Dockerfile#L78) as the `CMD`, I wrapped it with the [provision/entrypoint.sh](https://github.com/unfor19/nexus-ops/tree/master/provision/entrypoint.sh) script.

Bottom line, the whole logic is maintained in the `init-script` called [entrypoint.sh](https://github.com/unfor19/nexus-ops/blob/master/provision/entrypoint.sh).

Here's how the `main` function looks like

```bash
main(){
    start_nexus
    set_global_variables
    wait_for_healthy_response
    set_credentials
    change_initial_password
    enable_anonymous_access
    realms_enable_docker_token
    repositories_create_repository_wrapper "proxy" "docker-hub" "${_NEXUS_OPS_PATH}/repositories/docker-proxy-dockerhub.json"
    repositories_create_repository_wrapper "proxy" "docker-ecrpublic" "${_NEXUS_OPS_PATH}/repositories/docker-proxy-ecrpublic.json"
    repositories_create_repository_wrapper "group" "docker-group" "${_NEXUS_OPS_PATH}/repositories/docker-group.json"
    log_msg "Finished executing - ${_NEXUS_OPS_PATH}/entrypoint.sh"
}
```

## Final Result

Here's my project @ [GitHub - unfor19/nexus-ops](https://github.com/unfor19/nexus-ops). To understand how it all works, check the [provision/entrypoint.sh](https://github.com/unfor19/nexus-ops/tree/master/provision/entrypoint.sh) file; I tried to make it as neat as possible.

One more thing; you'll find the directory [provision/repositories](https://github.com/unfor19/nexus-ops/tree/master/provision/repositories) in my project. That directory contains the JSON structure of the provisioned repositories. As an example, here's the command for creating the [DockerHub repository](https://github.com/unfor19/nexus-ops/blob/master/provision/repositories/docker-proxy-dockerhub.json) 

```bash
repositories_create_repository_wrapper "proxy" "docker-hub" "${_NEXUS_OPS_PATH}/repositories/docker-proxy-dockerhub.json"
```

## Final Words

Initially, I was slightly intimidated by NXRM; it seemed complex to proxy Docker requests via NXRM. However, after creating this project, I think I got the idea, and I hope you did too.