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.

 

6 Comments

Leave a Reply

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