I have been working for 6 months in a new Java/Maven project in my company. It is quite exciting, we do a lot of cutting-edge technology and of course we use massively Jenkins for continous integration.

One of the problems that we started facing at one point is what to deliver. We were used to publish our WAR artifact to Artifactory, where it unfortunately layed there gaining only dust. Then our integration-test framework would run immediately after the build (triggered by mvn integration-test), and instead of retrieving the artifact, would use a new build run to generate the WAR in the target/ dir and start from there.

The test framework targets Amazon Web Services and from the Day One we decided to have everything dockerized. So the deployment script would do the following:

  1. build the WAR file again

  2. stream it to the AWS target environment using the target Docker’s REST API (ugh, really: we streamed a ByteArray of 16 Mb of data — more or less — in HTTP rest requests)

  3. on AWS, pull the Jetty base image from Docker hub

  4. on AWS, package a new image

  5. on AWS, spawn the container (together together with other ancillary services)

This is clearly not very efficient. We have a lot of tools at our avaiablity (Artifactory in primis) but we are not able to make them work in a meaningful way.

So I took myself the task to improve our workflow, thanks also to our DevOps specialist who explained me a lot of nuts’n’bolts of Docker.

It was clear that the best artifact to work with is not a WAR file, but a Docker image itself. I found out the wonderful Docker-maven-plugin by Spotify (check it out on their github repo) to really suit my need. With it I can create and publish the Docker image of my app directly on Artifactory with a single Maven goal.

Prerequisites

Your Artifactory should have been enabled to run as a "Docker Registry". This basically means that we are able to push and pull images to and from it.

The biggest example of a docker registry is the official Docker Hub where lots of mainstream porjects publish their public images.

Talk to your network administrator for all the concerns about it. You can also experiment by creating a Docker registry on your local machine while waiting for the IT guys to answer (it usally takes some time :P ). More informations on the Docker registry can be found in the Docker guides.

Second big point is, what kind of disk space on Artifactory do you have and what bandwidth availability. A well built Docker image should not require much more than a WAR/JAR artifact takes in terms of size and upload speed, but some tuning may be needed. My suggestion: try, try and retry. Docker is not easy to understand at the beginning.

Efficiency in Docker images creation

Long story short: the instruction order in your Dockerfile matters! Every line is a "layer" that Docker creates as basis for the next line, until you reach the end of the Dockerfile and the final image is done. The Docker Registry you use is a smart service, and if it founds out that some layers that the Docker image you are pushing are already pre-existing in its own belly, it will move on to the next layer (a.k.a. the next line of the Dockerfile) directly. This means that you want to add the WAR/JAR object in the image creation as later as possible, so that a large portion of the final image is in common with other builds of your project.

For my project, I use the official Jetty image from the Docker Hub onto which I then put my WAR, and produce a new image that I then upload to our private Artifactory registry. We achieved a delta factor between a new build and the previous build of about 31 Mbytes, whereas our WAR file weights about 26 Mbytes. Pretty neat!

This is what my Dockerfile would be:

FROM jetty:9.3.12-jre8
COPY mywarfile-build12345.war /opt/jetty/webapps/   # this line differentiates the images one another
ENTRYPOINT ["java", "-jar", "/opt/jetty/start.jar"]

You will see now how to produce the Docker image without needing any Dockerfile at all using Spotify’s Docker maven plugin.

Configuration

Let’s see how to configure the plugin in the project’s pom.xml:

<plugin>
  <groupId>com.spotify</groupId>
  <artifactId>docker-maven-plugin</artifactId>
  <version>0.4.13</version>
  <configuration>
    <imageTags>
      <imageTag>${project.version}-${myimage.build}</imageTag>
    </imageTags>
    <imageName>artifactory.mycompany.net/applications/myapp</imageName>
    <baseImage>jetty:9.3.12-jre8</baseImage>
    <entryPoint>["java", "-jar", "/opt/jetty/start.jar"]</entryPoint>
    <workdir>/opt/jetty</workdir>
    <exposes>8080</exposes>
    <resources>
      <resource>
        <targetPath>/opt/jetty/webapps/</targetPath>
        <directory>${project.build.directory}</directory>
        <include>${project.build.finalName}.war</include>
      </resource>
    </resources>
    <serverId>server_entry</serverId>
    <registryUrl>https://artifactory.mycompany.net</registryUrl>
  </configuration>
</plugin>

As you can see the parameters resemble pretty much the intended Dockerfile.

Notice that the server_entry is the label of an entry that you have to insert into your local ~/.m2/settings.xml as such:

<server>
  <id>server_entry</id>
  <username>insert_login_name_here</username>
  <password>insert_password_here</password>
  <configuration>
    <email>insert_your_email_here</email>
  </configuration>
</server>

Strangely enough the email field is mandatory for the current version of the spotify Docker-maven-plugin to work correctly, so watch out!

Usage

You can see that what I put in the pom.xml is parametrized by some automatic Maven variables (they look like ${something}). There is a residual parameter which is ${myimage.build} which in my case does not come from Maven internals, but from the command line: you can run the docker image generation like this:

mvn docker:build -Dmyimage.build=12345

and this will result in a Docker image created locally with tag

artifactory.mycompany.net/applications/myapp:0.0.1-SNAPSHOT-12345

(You can check this out with docker ps -a command). The 0.0.1-SNAPSHOT part here simply comes from the version of your project (on top of your pom.xml file), so it can be totally different in your case, of course.

To push this image to artifactory, we simply need to add a push command to the command line:

mvn docker:build -Dmyimage.build=12345 -DpushImage

That’s it.

If you use Jenkins CI like in my case, it is easy to inject the ${myimage.build} value from the Jenkins built-in variable $BUILD_NUMBER, so every successful job run will automatically push a different image version on Artifactory. Or you could just forget about it and remove the versioning entirely, and only use the latest tag (this is automatically managed by the Docker-maven-plugin again). Your choice :)

Have fun!