Skip to content

Building, tagging and pushing Docker images with Maven

A standard use case for Docker is to build a container to run a pre-built application so that the containerized app can be run on any Docker enabled host. The application and the container are sometimes developed and built separately. First the application is built, then a container is defined and built to include the application. However, it can be better to promote the Docker container to a first-class build artifact. That is, the build process always builds the deployed component and its container at the same time. This saves a manual build step and also ensures that the Docker container is always up to date with the latest application build. It allows us to easily develop and test against the Dockerized application directly – every build results in a new deployable container.

There are a number of ways to do this. This article looks at hooking the Docker tasks into the Maven build process.

The Docker Maven Plugin

The docker-maven-plugin from Spotify allows us to perform Docker operations from within Maven including build, tag and push images. The Docker operations can be run from command line through Maven but the real power comes from binding them to Maven build phases. This allows us to build new images on every build.

Build a Dockerfile

Building a Dockerfile from Maven is very straightforward. The plugin needs just a couple of configuration settings:

<plugin>
    <groupId>com.spotify</groupId>
    <artifactId>docker-maven-plugin</artifactId>
    <version>0.4.10</version>
    <configuration>
        <imageName>hotblac/${project.artifactId}</imageName>
        <dockerDirectory>${project.basedir}/docker</dockerDirectory>
    </configuration>
</plugin>

The <imageName> tag specifies the name of the image to be built, prefixed with your Docker Hub account name (hotblac in my case). ${project.artifactId} makes the image name the same as the Maven project name. The <dockerDirectory> location contains the Dockerfile and any resources required to build the image. If any build time resources need to be added (such as the built artifact), they can be specified in the <resources> section.

Running

mvn clean package docker:build

will build the Maven project and the specified Docker image.

Creating images without a Dockerfile

It’s also possible to build a Docker image entirely based on Maven configuration properties. This is pretty neat if all you want is a simple Java 8 Alpine container to run an executable jar.

<plugin>
	<groupId>com.spotify</groupId>
	<artifactId>docker-maven-plugin</artifactId>
	<version>0.4.10</version>
	<configuration>
		<imageName>hotblac/${project.artifactId}</imageName>
		<baseImage>java:openjdk-8-jdk-alpine</baseImage>
		<entryPoint>["java","-jar","/${project.build.finalName}.jar"]</entryPoint>
		<resources>
			<!-- copy the service's jar file from target into the root directory of the image -->
			<resource>
				<targetPath>/</targetPath>
				<directory>${project.build.directory}</directory>
				<include>${project.build.finalName}.jar</include>
			</resource>
		</resources>
	</configuration>
</plugin>

This example creates a Docker image containing files containing the project’s jar. Just the <baseImage> and <entryPoint> command need to be specified. This is a basic configuration to build an executable jar and a simple container to run it. Again, the Docker image can be built with:

mvn clean package docker:build

Binding to build phases

The real power of the plugin is here. By binding plugin goals to build phases, we can have Maven automatically build a Docker container on every build.

<execution>
	<id>build-image</id>
	<phase>package</phase>
	<goals>
		<goal>build</goal>
	</goals>
</execution>

This simply triggers the plugin’s build goal whenever the Maven package phase is invoked.

We can also have Maven tag and push images to a Docker repo (Docker Hub for example) whenever the project is deployed:

<execution>
	<id>tag-image-version</id>
	<phase>deploy</phase>
	<goals>
		<goal>tag</goal>
	</goals>
	<configuration>
		<image>hotblac/${project.artifactId}</image>
		<newName>docker.io/hotblac/${project.artifactId}:${project.version}</newName>
		<serverId>docker-hub</serverId>
		<pushImage>true</pushImage>
	</configuration>
</execution>
<execution>
	<id>tag-image-latest</id>
	<phase>deploy</phase>
	<goals>
		<goal>tag</goal>
	</goals>
	<configuration>
		<image>hotblac/${project.artifactId}</image>
		<newName>docker.io/hotblac/${project.artifactId}:latest</newName>
		<serverId>docker-hub</serverId>
		<pushImage>true</pushImage>
	</configuration>
</execution>

The syntax here is a little odd so let me explain.

  • <image> is the name of the image to be taggged / pushed.
  • <newName> is an alias for the image name with the name of the repository (docker.io in this case) prepended. This tells the plugin where to push the image.
  • <serverId> is a lookup for a server configuration in your Maven settings.xml file. This allows the Docker repo username / password to be external to the Maven POM file which is likely to be public.
  • <pushImage> is a switch for the plugin’s tag goal which pushes the image after tagging it. This allows us to tag and push in a single execution step. There is also a push plugin goal, but it’s not capable of tagging the image.
  • Finally, the whole execution configuration is copied twice. This is a workaround for Docker’s special behaviour for the ‘latest’ tag. We actually want to tag and push two images: the first tagged with the project version (${project.version}) and the second with the latest tag. I can’t figure out a nicer way to do this.

Now, we can simply run

mvn clean deploy

and Maven will:

  • Build your project’s main artifact (jar, war or whatever)
  • Build the Docker image from your Dockerfile or from the plugin’s configuration
  • Tag the image with the Maven project version number and latest
  • Push the image to the Docker repo

The result is that every time the project is deployed (or released), the Maven artifacts will be published to the Maven repo and the Docker images will be published to the Docker repo. The Docker repo will then contain runnable containers for every historic application build, with no additional steps in the build or release processes. Lovely.

Try it yourself!

This example worked particularly nicely with my Spring Boot application. Because Spring Boot packages web applications as self-contained executable jars rather than wars / ears to be deployed to an application server (such as Tomcat), deployment to Docker is very simple. We can simply copy the jar into a basic Java container and start it.

The source code for this example is available in the Spanners project, version 4.1 at GitHub. And of course, the resulting Docker containers are also available at Docker Hub.

 

Published inDockerHow To

6 Comments

  1. Gene Gene

    I’m trying to follow your tutorial but it’s not working…
    I’m trying to push to this repository= https://hub.docker.com/u/chuangg/
    ————————————————————————————————————-
    Here is my pom.xml:

    4.0.0

    com.gene.app
    VendingMachineDockerPush
    1.0-SNAPSHOT
    jar

    Maven Quick Start Archetype
    http://www.gene.com

    org.apache.maven.plugins
    maven-jar-plugin

    com.gene.sample.Customer_View

    org.apache.maven.plugins
    maven-compiler-plugin
    3.1

    1.7
    1.7

    junit
    junit
    4.8.2
    test

    build-docker

    1.8.0

    com.spotify
    docker-maven-plugin
    0.4.5

    chuangg/gene_docker_example
    <!– chuangg/${project.artifactId} //This doesn’t work! –>
    java:openjdk-8-jdk-alpine
    [“java”, “-jar”, “/${project.build.finalName}.jar”]

    /
    ${project.build.directory}
    ${project.build.finalName}.jar

    build-image
    package

    build

    tag-image-version
    deploy

    tag

    chuangg/gene_docker_example
    docker.io/chuangg/fuckyes1
    docker-hub
    true

    tag-image-latest
    deploy

    tag

    chuangg/gene_docker_example
    docker.io/chuangg/fuckyes1:latest
    docker-hub
    true

    gc
    https://index.docker.io/v1/
    default
    chuangg

    http://mojo.codehaus.org/my-project

    chuangg
    chuangg
    https://index.docker.io/v1/

    ————————————————————————————————————————–
    Inside my C:UsersUsername.m2settings.xml, I added this to the top of the xml file:

    docker-hub
    https://index.docker.io/v1/
    chuangg
    ******
    gc.genechuang@gmail.com

    ——————————————————————————————————————-
    The console result:

    Uploading: https://index.docker.io/v1/com/gene/app/VendingMachineDockerPush/1.0-SNAPSHOT/VendingMachineDockerPush-1.0-20161129.211015-1.jar
    Uploading: https://index.docker.io/v1/com/gene/app/VendingMachineDockerPush/1.0-SNAPSHOT/VendingMachineDockerPush-1.0-20161129.211015-1.pom

    Build Failure

    Failed to execute goal org.apache.maven.plugins:maven-deploy-plugin:2.7:deploy (default-deploy) on project VendingMachineDockerPush: Failed to deploy artifacts: Could not find artifact com.gene.app:VendingMachineDockerPush:jar:1.0-20161129.205056-1 in gc (https:/index.docker.io/v1/)

  2. Martin Long Martin Long

    when i try and run your example i get the following:

    com.spotify.docker.Resource – class not found, I’m guessing its my environment, Any ideasm thanbks

    [ERROR] Failed to execute goal com.spotify:docker-maven-plugin:0.4.10:build (def
    ault-cli) on project spanners: Unable to parse configuration of mojo com.spotify
    :docker-maven-plugin:0.4.10:build: Error loading class ‘com.spotify.docker.Resou
    rce’ -> [Help 1]
    [ERROR]
    [ERROR] To see the full stack trace of the errors, re-run Maven with the -e swit

    com.spotify.docker.Resou
    rce

  3. Anders Anders

    Hi Stuart

    Do you thinkt that this plugin-approact can be combined with multistage dockerfiles? I.e. a dockerfile, in which the first stage creates an image with Maven and JDK installed, and builds the jar/war, and the second stage builds the runtime image, i.e. an image with JRE and the app installed.

    I would like to be able to build and deploy the jar/war and the docker image both locally from a development environment (where Maven etc is installed) and from a generic build environment, where only docker is installed and I can’t figure out whether these requirements fit with the plugin you mention.

    In the dev env one would do like you descrive: ‘mvn clean deploy’.
    In the build server env one would have to issue some docker command on the checked out repository.

    • Hi Anders
      The plugin certainly can invoke a multistage dockerfile but I’m not sure it’s quite what you’re looking for.

      Yes, you can create a dockerfile with Maven etc installed to build your component. But then if you have that, why invoke it from Maven on your dev environment / host? It would work, if you’ve a particular reason to invoke from Maven (you may well do!) but it would also work if you built using the docker build command.

Leave a Reply

Your email address will not be published. Required fields are marked *