Sonotype Nexus artifactory notes

Russell Bateman
April 2019
last update:

Sonatype Nexus Repository OSS is a free artifact repository (artifactory) with universal support for popular formats.


Nexus links

Repository types

In Nexus, there are three repository types:

1. Proxy repositories —for access to public repositories, like a doorway to a public repository, in fact a link to a remote repository. When no local component is found, the request is forwarded to the remote repository.

Think Maven here; keeping high-use JARs locally for consumption, especially when your Internet connection is down while Maven is forwarded on to Maven Central in the case of artifacts that aren't held locally. Copies retrieved from remote repositories remain in the Nexus proxy repository until removed by the administrator.

Or think Docker; Docker Hub is targeted.

maven-central

2. Hosted repository —for use by your development organization, but not public. In case of missing component, no forwarding on (to Maven Central, Docker Hub, etc.) is done. This is for use by your organization to host internal artifacts that should not be made public.

It's also a place to put third-party artifacts that aren't available publicly.

maven-snapshots,*
maven-releases

3. Repository groups —allows host repositories to be accessed from a single URL. This combines many repositories into one. For example, maven-public is a group that includes maven-central, maven-releases and maven-snapshots into one for easy access (instead of listing all three).

maven-public

* Note that maven-snapshots is special: no artifacts can be put into it that do not have a version ending in -SNAPSHOT.


Creating a new repository in Nexus

I'm going to create a place to use as an artifactory for Docker containers.

  1. In the (black) title bar, click the gear icon (Server administration and configuration).
  2. Either in the left-hand margin or in the Repository content region, click Repositories.
  3. In the Repositories (Manage repositories) content region, click the button + Create repository and choose one of the (premade) repository types listed, perhaps docker (proxy).
  4. Give the new repository a name.
  5. Give the new repository the URL of the proxied repository, i.e.: Maven Central, Docker Hub, etc.
  6. If using a well known proxy, like Maven Central or Docker Hub, select the correspondingradio button. A certificate may be set up (because if the request goes all the way to Docker Hub, log-in is required—as this is a non-interactive process, a certificate can be used in lieu of username and password), or...
  7. ...establish authentication by username and password.

How to specify your Nexus repository in pom.xml

First, amend the ~/.m2/settings.xml file of every build user to contain:

settings.xml:
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.1.0"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.1.0
                     http://maven.apache.org/xsd/settings-1.1.0.xsd">
  <servers>
    <server>
      <id>nexus-snapshots</id>
      <username>admin</username>
      <password>admin123</password>
    </server>
    <server>
      <id>nexus-releases</id>
      <username>admin</username>
      <password>admin123</password>
    </server>
  </servers>

  <mirrors>
    <mirror>
      <id>central</id>
      <name>central</name>
      <url>http://your-host:8081/repository/maven-group/</url>
      <mirrorOf>*</mirrorOf>
    </mirror>
  </mirrors>
</settings>

In your project pom.xml, you'll need to set up Nexus as a repository. Remember (see in later notes) that a Nexus repository can be a proxy repository for Maven Central or others.

pom.xml:
<project ...>
  ...

  <repositories>
    <repository>
      <id>maven-group</id>
      <url>http://your-host:8081/repository/maven-group/</url>
    </repository>
  </repositories>

  ...

<dependencies>
  <dependency>
    (reference JARs you want to get from Nexus or Maven Central, etc.)
  </dependency>
</dependencies>
</project>

Upon issuing mvn {compile,test,install, etc.}, Maven will go to your Nexus instance.

There are pom.xml-based instructions you can add if you wish to publish what you build to your repository. See this page.


How to specify your Nexus repository for Docker

First, configure the Docker dæmon for your host to accept to work with HTTP instead of HTTPS. This is usually done by modifying /etc/docker/daemon.json thus. If this file doesn't exist, create it:

{
  "insecure-registries" :
  [
    "your-repo:8082",
    "your-repo:8083"
  ],
  "disable-legacy-registry" : true
}

Restart Docker (sudo systemctl restart docker).

You'll have to authenticate your host to the artifactory thus:

$ docker login -u admin -p admin123 your-repo:8082 pulling
$ docker login -u admin -p admin123 your-repo:8083 pushing

...which will create an entry in ~/.docker/config.json with authorizations:

{
  "auths" :
  {
    "your-repo:8082" : { "auth" : "YWRtaW46YWRtaW4xMjM=" },
    "your-repo : 8083": { "auth" : "YWRtaW46YWRtaW4xMjM=" }
}

To pull images (e.g.: alpine) from the artifactory, use this command

$ docker pull your-repo:8082/httpd:2.4-alpine

To get information about tagging and pushing your container images, see this page.


My first experience uploading from command line

I just wanted to get something—anything—to upload to Nexus. I happened to have an image and that works as a test.

$ curl -v -u admin:admin123 --upload-file notre-dame.jpg http://maven.acme.com:8081/repository/testing/com/example/notre-dame/1.0.0/notre-dame-1.0.0.jpg
*   Trying 10.10.10.164...
* TCP_NODELAY set
* Connected to maven.acme.com (10.10.10.164) port 8081 (#0)
* Server auth using Basic with user 'admin'
> PUT /repository/testing/com/example/notre-dame/1.0.0/notre-dame-1.0.0.jpg HTTP/1.1
> Host: maven.acme.com:8081
> Authorization: Basic YWRtaW46YWRtaW4xMjM=
> User-Agent: curl/7.58.0
> Accept: */*
> Content-Length: 100939
> Expect: 100-continue
>
< HTTP/1.1 100 Continue
* We are completely uploaded and fine
< HTTP/1.1 201 Created
< Date: Tue, 16 Apr 2019 21:03:05 GMT
< Server: Nexus/3.10.0-04 (OSS)
< X-Frame-Options: SAMEORIGIN
< X-Content-Type-Options: nosniff
< Content-Security-Policy: sandbox allow-forms allow-modals allow-popups allow-presentation allow-scripts
allow-top-navigation
< X-Content-Security-Policy: sandbox allow-forms allow-modals allow-popups allow-presentation allow-scripts
allow-top-navigation
< Content-Length: 0
<
* Connection #0 to host maven.acme.com left intact

Here's how I got pom.xml and JARs to go up
$ curl -v -u admin:admin123 --upload-file com/acme/psjbase/986/psjbase-986.jar      http://maven.acme.com:8081/repository/testing/com/acme/psjbase/986/psjbase-986.jar
$ curl -v -u admin:admin123 --upload-file com/acme/psjbase/986/psjbase-986.jar.md5  http://maven.acme.com:8081/repository/testing/com/acme/psjbase/986/psjbase-986.jar.md5
$ curl -v -u admin:admin123 --upload-file com/acme/psjbase/986/psjbase-986.jar.sha1 http://maven.acme.com:8081/repository/testing/com/acme/psjbase/986/psjbase-986.jar.sha1
$ curl -v -u admin:admin123 --upload-file com/acme/psjbase/986/psjbase-986.pom      http://maven.acme.com:8081/repository/testing/com/acme/psjbase/986/psjbase-986.pom


Deploying artifacts to Nexus

I want to get Nexus working for deploying JARs to it. I'll be following the page linked at the bottom of this note, or Using Nexus 3 as Your Repository—Part 1: Maven Artifacts. Here's my first stab at that. For posterity (myself), I'm going to detail the entire struggle since knowing how I failed is almost as useful as knowing how I succeeded. Sorry, it's going to get ugly.

First, in order to understand this thoroughly, I think I'll start from scratch including my own copy of Nexus. I'll use host moria, for the installation. It's a VM running Ubuntu Server 18.04.2 LTS Bionic Beaver.

See the next two sections. In another note I'll handle fronting Docker Hub using Nexus.


Installing Nexus in a container
russ@moria:~/dev/nexus$ docker pull sonatype/nexus3
Using default tag: latest
latest: Pulling from sonatype/nexus3
8ba884070f61: Pull complete
10f65a60e03d: Pull complete
e4bf25d22017: Pull complete
Digest: sha256:664306d8b367ff2ebf7ff3272560404d34ec9b611826d9777c4fd5b3c8b79b7a
Status: Downloaded newer image for sonatype/nexus3:latest
russ@moria:~/dev/nexus$ docker run -d -p 8081:8081 --name nexus sonatype/nexus3
33cfd17ee5a58871b8ced2f1601911c778c13bd6948f8a1c65151a1f927dc003

I'm doing this test from a different host, nargothrond. Note also that one should not jump the gun immediately: it takes Nexus two to three minutes to launch in a new container. Before that finishes, responses to curl or answering a browser GET may be refused; this doesn't mean that Nexus isn't working.

russ@nargothrond: ~ $ curl -u admin:admin123 http://moria:8081/service/metrics/ping
curl: (7) Failed to connect to moria port 8081: Connection refused
(Because moria was intentionally rebooted, ...)
russ@nargothrond ~ $ curl -u admin:admin123 http://moria:8081/service/metrics/ping
pong
(And, on moria itself:)
russ@moria:~/dev/nexus$ curl -u admin:admin123 http://localhost:8081/service/metrics/ping
pong

So far, so good. Note (to self) that, just as for Kibana, I can't access Nexus via Chrome, only via Firefox or an incognito Chrome window, because Chrome insists on using HTTPS rather than HTTP even if you specify the latter.

http://moria:8081/

I sign in using admin/admin123. Clicking on Browse, I see the following assets and components (repositories). I'm concentrating on Maven, of course. Note that all the repositories are empty (as this is a new Nexus installation).

Now, it's important to map additional ports, 8082, 8083, in the Docker run command in order to be able to pull and push from Maven. To reissue the run command, it will be necessary to remove the container, and its name, then re-run it:

russ@moria:~/dev/nexus$ docker ps -a
CONTAINER ID   IMAGE             COMMAND                    CREATED        STATUS       PORTS                    NAMES
7a8a9480d89f   sonatype/nexus3   "sh -c ${SONATYPE_DI..."   2 hours ago    Up 2 hours   0.0.0.0:8081->8081/tcp   nexus
russ@moria:~/dev/nexus$ docker stop 7a8a9480d89f
7a8a9480d89f
russ@moria:~/dev/nexus$ docker rm 7a8a9480d89f
7a8a9480d89f
russ@moria:~/dev/nexus$ docker run -d -p 8081:8081 -p 8082:8082 -p 8083:8083 --name nexus sonatype/nexus3
d48805b220159dee6d59ef5b78acf8119f96de2f5ac58330576f12ef3b4767ef
russ@moria:~/dev/nexus$ docker ps -a
CONTAINER ID   IMAGE             COMMAND                    CREATED        STATUS        PORTS                              NAMES
d48805b22015   sonatype/nexus3   "sh -c ${SONATYPE_DI..."   2 minutes ago  Up 2 minutes  0.0.0.0:8081-8083->8081-8083/tcp   nexus

(If you had left the browser window open at Nexus, you probably noticed that you lost connection, then got it back after restart.)

Host-resident volume for Nexus data: a big mess...

If we wanted to force Nexus, which is running in a Docker container, nevertheless to place its data container in the filesystem of the host running it, we could append another option to the Docker run command. This is desirable because we don't see Nexus data as ephemeral. We want to persist it. We'd like to dedicate subdirectory /opt/nexus-data on our host, moria, to be where Docker sets up Nexus' data volume, also named /nexus-data. But, we cannot do this. You'll notice that... (protracted, unhappy discusion here)

$ docker run -d -p 8081:8081 -p 8082:8082 -p 8083:8083 \
                               -v /opt/nexus-data:/nexus-data \
                               --name nexus sonatype/nexus3
root@moria:/opt# ll
total 16
drwxr-xr-x  4 root root 4096 Apr 24 13:02 .
drwxr-xr-x 23 root root 4096 Apr 24 11:00 ..
drwx--x--x  4 root root 4096 Apr 23 12:16 containerd
drwxr-xr-x  2 root root 4096 Apr 24 13:02 nexus-data

However, because this subdirectory belongs to root, the container will not remain up—unable to write to it. Observe this diagnosis:

russ@moria:~/dev/nexus$ docker ps -a
CONTAINER ID   IMAGE             COMMAND                    CREATED         STATUS                      NAMES
bbec00ca76b1   sonatype/nexus3   "sh -c ${SONATYPE_DI..."   7 seconds ago   Exited (255) 1 second ago   nexus
russ@moria:~/dev/nexus$ docker logs bbec00ca76b1
mkdir: cannot create directory '../sonatype-work/nexus3/log': Permission denied
mkdir: cannot create directory '../sonatype-work/nexus3/tmp': Permission denied
Warning:  Cannot open log file: ../sonatype-work/nexus3/log/jvm.log
Warning:  Forcing option -XX:LogFile=/tmp/jvm.log
OpenJDK 64-Bit Server VM warning: Cannot open file ../sonatype-work/nexus3/log/jvm.log due to No such file or directory

java.io.FileNotFoundException: ../sonatype-work/nexus3/tmp/i4j_ZTDnGON8hezynsMX2ZCYAVDtQog=.lock (No such file or directory)
	at java.io.RandomAccessFile.open0(Native Method)
	at java.io.RandomAccessFile.open(RandomAccessFile.java:316)
	at java.io.RandomAccessFile.(RandomAccessFile.java:243)
	at com.install4j.runtime.launcher.util.SingleInstance.check(SingleInstance.java:72)
	at com.install4j.runtime.launcher.util.SingleInstance.checkForCurrentLauncher(SingleInstance.java:31)
	at com.install4j.runtime.launcher.UnixLauncher.checkSingleInstance(UnixLauncher.java:88)
	at com.install4j.runtime.launcher.UnixLauncher.main(UnixLauncher.java:67)
Unable to update instance pid: Unable to create directory /nexus-data/instances
/nexus-data/log/karaf.log (No such file or directory)
Unable to update instance pid: Unable to create directory /nexus-data/instances

Now, tweak /opt/nexus-data (unnaturally) to be writable and you'll see that it works:

root@moria:/opt# chmod a+rwx nexus-data/
root@moria:/opt# ll
total 16
drwxr-xr-x  4 root root 4096 Apr 24 13:38 .
drwxr-xr-x 23 root root 4096 Apr 24 11:00 ..
drwx--x--x  4 root root 4096 Apr 23 12:16 containerd
drwxrwxrwx  2 russ russ 4096 Apr 24 13:38 nexus-data
russ@moria:~/dev/nexus$ docker run -d -p 8081:8081 -p 8082:8082 -p 8083:8083 \
                                                   --volume /opt/nexus-data:/nexus-data \
                                                   --name nexus sonatype/nexus3
6cee176ef003b44be7a1adc5696144d68d22c9f9889c95b4753e584068ba86c0
russ@moria:~/dev/nexus$ docker ps -a
CONTAINER ID   IMAGE             COMMAND                   CREATED         STATUS         PORTS                              NAMES
6cee176ef003   sonatype/nexus3   "sh -c ${SONATYPE_DI..."  4 seconds ago   Up 2 seconds   0.0.0.0:8081-8083->8081-8083/tcp   nexus
russ@moria:~/dev/nexus$ docker exec -it 6cee176ef003 bash
bash-4.2$ whoami
nexus

User nexus must be, then, the owner of /opt/nexus-data in order to be able to write to it. However, this user doesn't exist on the host:

root@moria:/opt# cat /etc/passwd | grep nexus

Since Docker 1.7, this is a proposed solution, but it doesn't work, especially in the presence of SELinux. It involves adding a third part to the volume-mapping option of docker run:

The Nexus Docker Hub read-me gives this as a solution, but it doesn't work either because, no matter what, the host path ends up being created by root. I'm betting that they always run Docker as root, so they've never noticed this screw-up. You can only reasonably pre-create the mapped filesystem entry point, establish its ownership and privileges as promiscuous, then launch Docker:

russ@moria:~/dev/nexus$ mkdir nexus-data
russ@moria:~/dev/nexus$ chmod a+w nexus-data/
russ@moria:~/dev/nexus$ ll -d nexus-data/
drwxrwxrwx 2 russ russ 4096 Apr 24 14:36 nexus-data/
russ@moria:~/dev/nexus$ docker volume create --name nexus-data
nexus-data
russ@moria:~/dev/nexus$ docker run -d -p 8081:8081 -p 8082:8082 -p 8083:8083 \
                              --volume /home/russ/dev/nexus/nexus-data:/nexus-data \
                              --name nexus sonatype/nexus3

This sucks badly. The only real way to do this is by way of Docker Compose. I'm not going to cover that right now. So, this saga was not too helpful except for what there was to learn from it.

Back, instead, to our work with the artifactory (enough of Docker now)...


Deploying artifacts to Nexus from the deploy Maven lifecycle

Note: If you create a new blob store for each new repository you create, the data for it will be in a different subdirectory under nexus-data. I'll look into the significance of this later, I hope, but this is only polish to be rubbed on the fender.

~/.m2/settings.xml:

This doesn't trash Maven for the entire host you're on—only the user that does it. Normally, at least in the past, I've never generally even had a settings.xml at ~/.m2, so this file is completely devoted to my use of Nexus (and nothing else, that is, the contents aren't prescribed by any other consideration).

<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.1.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.1.0
                         http://maven.apache.org/xsd/settings-1.1.0.xsd">
  <servers>
    <server>
      <id>nexus-central</id>
      <username>admin</username>
      <password>admin123</password>
    </server>
    <server>
      <id>nexus-snapshots</id>
      <username>admin</username>
      <password>admin123</password>
    </server>
    <server>
      <id>nexus-releases</id>
      <username>admin</username>
      <password>admin123</password>
    </server>
  </servers>

  <mirrors>
    <mirror>
      <id>central</id>
      <name>central</name>
      <url>http://moria:8081/repository/maven-central/</url>
      <mirrorOf>*</mirrorOf>
    </mirror>
  </mirrors>

  <profiles>
    <profile>
      <id>nexus</id>
      <!--Enable snapshots for the built in central repo to direct -->
      <!--all requests to nexus via the mirror -->
      <repositories>
        <repository>
          <id>central</id>
          <url>http://central</url>
          <releases><enabled>true</enabled></releases>
          <snapshots><enabled>true</enabled></snapshots>
        </repository>
      </repositories>
     <pluginRepositories>
        <pluginRepository>
          <id>central</id>
          <url>http://central</url>
          <releases><enabled>true</enabled></releases>
          <snapshots><enabled>true</enabled></snapshots>
        </pluginRepository>
      </pluginRepositories>
    </profile>
  </profiles>
  <activeProfiles>
    <!--make the profile active all the time -->
    <activeProfile>nexus</activeProfile>
  </activeProfiles>
</settings>
pom.xml:

This is a very simple pom.xml file for a tiny project I once did. The lines specifically added to it to make it go to my Nexus artifactory first, then to Maven, are highlighted. Of course, moria is hardly an obvious name for my Nexus artifactory: it would be better to name it something mneumonic.

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                        http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.acme</groupId>
  <artifactId>json-to-xml</artifactId>
  <version>1.0.0-SNAPSHOT</version>
  <name>json-to-xml</name>
  <description>JSON-to-XML filter not requiring a POJO description of the JSON.</description>

  <properties>
    <log4j.version>2.1</log4j.version>
    <junit.version>4.12</junit.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <repositories>
    <repository>
      <id>maven-central</id>
      <url>http://moria:8081/repository/maven-central/</url>
    </repository>
  </repositories>

  <dependencies>
    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-api</artifactId>
      <version>${log4j.version}</version>
    </dependency>
    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-core</artifactId>
      <version>${log4j.version}</version>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>${junit.version}</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <sourceDirectory>src/main/java</sourceDirectory>
    <testSourceDirectory>src/test/java</testSourceDirectory>
    <resources>
      <resource>
        <directory>src</directory>
        <excludes>
          <exclude>**/*.java</exclude>
        </excludes>
      </resource>
    </resources>
    <plugins>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.8</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
        </configuration>
      </plugin>
      <plugin>
        <artifactId>maven-assembly-plugin</artifactId>
        <configuration>
          <archive>
            <manifest>
              <mainClass>com.acme.filter.JsonToXmlFilter</mainClass>
            </manifest>
          </archive>
          <descriptorRefs>
            <descriptorRef>jar-with-dependencies</descriptorRef>
          </descriptorRefs>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

Next up: using pom.xml to update the Nexus artifactory with newly created artifacts. This is done by an addition that I'm going to put at the end. (For some reason, my source-code display macros are lower-casing identifiers in the segment below: beware!)

  
    
      nexus-snapshots
      http://moria:8081/repository/maven-snapshots/
    
    
      nexus-releases
      http://moria:8081/repository/maven-releases/
    
  

This artifact pushing will never go "higher" than Nexus (because we do not have rights in places like Maven Central, etc.). This is how it works:

$ mvn deploy
  1. To add a new snapshot, that is, an artifact whose version ends with SNAPSHOT, e.g.: 1.0.0-SNAPSHOT. This is the paragraph containing <id>nexus-snapshots</id> and the result will go to Nexus' maven-snapshots repository.
  2. To add a new release, that is, anything other than SNAPSHOT. This is the paragraph containing <id>nexus-releases</id> and the result will go to Nexus' maven-releases repository.

Right now, on my test server (moria), these two repositories are empty. Indeed, after running it, I see this:

tester@nargothrond:~/dev/json-to-xml$ mvn deploy
tester@nargothrond:~/.m2$ tree repository/com/acme/
repository/com/acme/
└── json-to-xml
    ├── 1.0.0-SNAPSHOT
    │   ├── json-to-xml-1.0.0-SNAPSHOT.jar
    │   ├── json-to-xml-1.0.0-SNAPSHOT.pom
    │   ├── maven-metadata-local.xml
    │   └── _remote.repositories
    └── maven-metadata-local.xml

2 directories, 5 files

...and I see the same thing in Nexus. Notice all the noise at the end of Maven's run that says it all:


tester@nargothrond:~/dev/json-to-xml$ mvn deploy
.
.
.
[INFO] --- maven-deploy-plugin:2.7:deploy (default-deploy) @ json-to-xml ---
Downloading from nexus-snapshots: http://moria:8081/repository/maven-snapshots/com/acme/json-to-xml/1.0.0-SNAPSHOT/maven-metadata.xml
Uploading to nexus-snapshots: http://moria:8081/repository/maven-snapshots/com/acme/json-to-xml/1.0.0-SNAPSHOT/json-to-xml-1.0.0-20190424.231519-1.jar
Uploaded to nexus-snapshots: http://moria:8081/repository/maven-snapshots/com/acme/json-to-xml/1.0.0-SNAPSHOT/json-to-xml-1.0.0-20190424.231519-1.jar (27 kB at 256 kB/s)
Uploading to nexus-snapshots: http://moria:8081/repository/maven-snapshots/com/acme/json-to-xml/1.0.0-SNAPSHOT/json-to-xml-1.0.0-20190424.231519-1.pom
Uploaded to nexus-snapshots: http://moria:8081/repository/maven-snapshots/com/acme/json-to-xml/1.0.0-SNAPSHOT/json-to-xml-1.0.0-20190424.231519-1.pom (2.8 kB at 60 kB/s)
Downloading from nexus-snapshots: http://moria:8081/repository/maven-snapshots/com/acme/json-to-xml/maven-metadata.xml
Uploading to nexus-snapshots: http://moria:8081/repository/maven-snapshots/com/acme/json-to-xml/1.0.0-SNAPSHOT/maven-metadata.xml
Uploaded to nexus-snapshots: http://moria:8081/repository/maven-snapshots/com/acme/json-to-xml/1.0.0-SNAPSHOT/maven-metadata.xml (782 B at 18 kB/s)
Uploading to nexus-snapshots: http://moria:8081/repository/maven-snapshots/com/acme/json-to-xml/maven-metadata.xml
Uploaded to nexus-snapshots: http://moria:8081/repository/maven-snapshots/com/acme/json-to-xml/maven-metadata.xml (292 B at 8.6 kB/s)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.767 s
[INFO] Finished at: 2019-04-24T17:15:19-06:00
[INFO] ------------------------------------------------------------------------

Next, I'll change the version of my build artifact to a proper version number, 1.0.9, and try again. Afterward, I see the artifacts installed in ~/.m2/repository/com/acme/json-to-xml as well as Nexus:


tester@nargothrond:~/dev/json-to-xml$ mvn deploy
.
.
.
[INFO] --- maven-deploy-plugin:2.7:deploy (default-deploy) @ json-to-xml ---
Uploading to nexus-releases: http://moria:8081/repository/maven-releases/com/acme/json-to-xml/1.0.9/json-to-xml-1.0.9.jar
Uploaded to nexus-releases: http://moria:8081/repository/maven-releases/com/acme/json-to-xml/1.0.9/json-to-xml-1.0.9.jar (27 kB at 228 kB/s)
Uploading to nexus-releases: http://moria:8081/repository/maven-releases/com/acme/json-to-xml/1.0.9/json-to-xml-1.0.9.pom
Uploaded to nexus-releases: http://moria:8081/repository/maven-releases/com/acme/json-to-xml/1.0.9/json-to-xml-1.0.9.pom (2.8 kB at 68 kB/s)
Downloading from nexus-releases: http://moria:8081/repository/maven-releases/com/acme/json-to-xml/maven-metadata.xml
Uploading to nexus-releases: http://moria:8081/repository/maven-releases/com/acme/json-to-xml/maven-metadata.xml
Uploaded to nexus-releases: http://moria:8081/repository/maven-releases/com/acme/json-to-xml/maven-metadata.xml (312 B at 9.5 kB/s)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.665 s
[INFO] Finished at: 2019-04-24T17:18:02-06:00
[INFO] ------------------------------------------------------------------------

Maven repositories, an illustration

This is to underscore the narrative that Maven...

  1. ...stores artifacts (JARs, mostly) during, and as a result of its build, in a local repository on the development host,

  2. Sonatype Nexus (or any other corporate artifactory implementation) buffers those artifacts for the development organization as a whole including to act as "public" repository for proprietary artifacts (see Repository types),

  3. Nexus, in turn, negotiates retrieval of artifacts from the public realm and can even govern availability of public artifacts (or versions thereof) to development in-house.

  4. Maven is guided in all of this by a) statements about repository sources and dependency management in the project pom.xml(s) and b) by ~/.m2/settings.xml.


How to create a user and give it permissions to deploy

Note: As best practice, the first-time administrator should create a new user and assign it with the Administrator role (i.e.: not use user admin), then give user admin a real password (such that no one else can use it).

Note: It's also possible to use LDAP or other frameworks to manage users, roles, permissions and this is how things should be done. This is because, without that, you're reduced to putting usernames and password into exposed places like ~/.m2/settings.xml (see notes above on "Deploying artifacts to Nexus"). The notes in here, however, presume simple, native Nexus user and role management.

As it comes with the software, the Nexus admin user is powerful enough to deploy artifacts via a) the ReST interface, b) curl or c) Maven to Nexus.



Administrator users (including admin) can create users. These are typically external users. It's pretty obvious how to create a user and assign a password.

To endow the user with abilities, you need first to create named roles beyond the couple (nx-admin and nx-anonymous) Nexus offers. To do this:

  1. Go to the "gear" or settings icon.
  2. Under Security choose Roles.
  3. Click Create role.
  4. Select Nexus rules.
  5. Create Role ID, artifactory-deployment.
  6. Create Role name, artifactory-deployment.
  7. Describe role, Ability to deploy artifacts to Nexus
  8. Scroll through Available to nx-repository-admin-*-*-* and click the > button to copy this and all other nx-repository- to Given.
  9. Click Create role

Now edit the user you wish to endow with abilities:

  1. Under Security, click Users.
  2. Select the user to be endowed.
  3. Under Roles, select the ability you wish to bestow and click the > button to move it to Granted.
  4. Click the Saved button.

Failed to find artifact...

Maven reports that it can't find an artifact; you're trying to put the artifact up to Nexus (of course it's not there—yet). This is an awkward attempt at telling you you've misspelled the repository name which the message below can't find as maven-released (because it's maven-releases).


Failed to deploy artifacts: Could not find artifact lpg.runtime.java:lpg.runtime.java:jar:2.0.17.v201004271640 \
in maven-released (http://maven.acme.com:8081/repository/maven-released/)

Still failing with 401...
Failed to deploy artifacts: Could not transfer artifact Failed to transfer file:
  Return code is: 401, ReasonPhrase: Unauthorized.

Etc.

After Googling this problem to death, it's pretty clear that the 401 is next to meaningless. Ultimately, whatever other problems I had (and that were not fixed by the exercise in creating a new user with roles) were put to rest. The real reason was the myriad, fantastic Nexus artifactory paths reported in blog posts, documentation and stackoverflow out there—all tragically wrong! The only way to compose a proper URL (for use under your pom.xml's <distributionManagement> paragraph is to...

  1. ...reach Nexus in your browser,
  2. browse to the repository you're trying to hit, likely one of maven-releases or maven-snapshots, and, toward the right side, under URL, click the Copy button to see what it says.

As I've intimated here, that path was considerably different from all the suggestions in the help I read on the way to conquering this problem. Moral of the story: you can't trust what anyone says your real path is going to be. Mine was http://maven.acme.com:8081/repository/maven-releases/.

Also, it is imperative that the <servers> <id>s noted in ~/.m2/settings.xml and used in the pom.xml's <distributionManagement> paragraph match rigorously.

Here are the (temporary) pom.xml I'm using (because I'm issuing mvn deploy:deployFile directly from a script. (This is because it's for ant builds for which there's no pom.xml, and I need the artifacts in a usable place.) Here also is part of my settings.xml:

pom.xml:

Yeah, literally groupId and artifactId of "nothing" and Maven doesn't complain or choke because it's not really trying to what a pom.xml would suggest. I'm running the script I've given here.* The only purpose of this file is to tell Maven—and I couldn't find any other way—what's in the <distributionManagement> paragraph.

* This script is set up to generate the temporary pom.xml automatically, though the URL inside must be modified to fit the instance.

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                             http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>nothing</groupId>
  <artifactId>nothing</artifactId>
  <version>nothing</version>
  <description>Temporary pom.xml to make Maven work.</description>

  <distributionManagement>
    <repository>
      <id>maven-releases</id>
      <name>maven-releases</name>
      <url>http://maven.acme.com:8081/repository/maven-releases</url>
      <layout>default</layout>
    </repository>
    <snapshotRepository>
      <id>maven-snapshots</id>
      <name>maven-snapshots</name>
      <url>http://maven.acme.com:8081/repository/maven-snapshots/</url>
      <layout>default</layout>
    </snapshotRepository>
  </distributionManagement>

</project>
~/.m2/settings.xml:
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.1.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.1.0
                              http://maven.apache.org/xsd/settings-1.1.0.xsd">
  <servers>
    <server>
      <id>maven-releases</id>
      <username>admin</username>
      <password>admin123</password>
    </server>
    <server>
      <id>maven-snapshots</id>
      <username>admin</username>
      <password>admin123</password>
    </server>
    <server>
      <id>maven-public</id>
      <username>admin</username>
      <password>admin123</password>
    </server>
  </servers>

  <!-- Mirror all external repositories in Nexus, i.e.: pull down everything to Nexus. -->
  <mirrors>
    <mirror>
      <id>nexus</id>
      <mirrorOf>*</mirrorOf>
      <url>http://maven.acme.com:8081/repository/maven-central/</url>
    </mirror>
  </mirrors>

  <profiles>
    <profile>
      <id>nexus</id>
      <!-- Enable snapshots for the built in Central repository to redirect
					 -->
      <!-- all requests to Nexus via the mirror. -->
      <repositories>
        <repository>
          <id>central</id>
          <name>central</name>
          <url>http://maven.acme.com:8081/repository/maven-central/</url>
          <releases><enabled>true</enabled></releases>
          <snapshots><enabled>true</enabled></snapshots>
        </repository>
      </repositories>
     <pluginRepositories>
        <pluginRepository>
          <id>central</id>
          <url>http://central</url>
          <releases><enabled>true</enabled></releases>
          <snapshots><enabled>true</enabled></snapshots>
        </pluginRepository>
      </pluginRepositories>
    </profile>
  </profiles>
  <activeProfiles>
    <!--make the profile active all the time -->
    <activeProfile>nexus</activeProfile>
  </activeProfiles>
</settings>