Maven-Based Docker Projects

An explanation of how we wrap all of our Docker-based projects in maven project structures for consistency and simplicity in our build pipelines.


6 min read
Maven-Based Docker Projects

At Single, we wrap almost all of our docker builds in maven projects. Maven is a Java-based build and dependency management tool. While its main use is for JVM-based projects, it can also be used alongside docker to build images and run containers, which is the approach we will be discussing here.

Why?

The main reason we decided to wrap all of our Docker-based projects in maven projects is for consistency. The same command will create a docker container no matter what kind of component is being built — whether that’s a microservice, a database, or an Angular-based UI project. Similarly, when developers want to run the containers locally, one command will spin up any project. Aside from the ease of use on the development side, this consistency in project configuration makes our CI/CD configuration and scripts much simpler. The CI process only needs to have java available and know how to execute maven commands to build Docker images.

An example

We’ve created an example project to demonstrate what our project setup looks like for building docker containers. Let’s examine the pieces of the project to show how we have it all setup.

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<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.singlemusic.example</groupId>
    <artifactId>nginx-docker-example</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <name>nginx-docker-example</name>
    <description>Example demonstrating maven project structure for docker builds</description>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

        <docker.verbose>true</docker.verbose>
        <docker.plugin.version>0.23.0</docker.plugin.version>
        
        <docker.image.name>${project.artifactId}</docker.image.name>
        <docker.network.name>local-network</docker.network.name>

        <docker.build.label.build_date>1970-01-01T00:00:00Z</docker.build.label.build_date>
        <docker.build.label.vcs_url>local</docker.build.label.vcs_url>
        <docker.build.label.vcs_ref>local</docker.build.label.vcs_ref>
        <docker.build.label.vendor>single music</docker.build.label.vendor>
        <docker.build.label.version>local</docker.build.label.version>
        <docker.build.label.schema_version>1.0</docker.build.label.schema_version>
    </properties>

    <build>
        <plugins>
            <!-- tag::docker[] -->
            <plugin>
                <groupId>io.fabric8</groupId>
                <artifactId>docker-maven-plugin</artifactId>
                <version>${docker.plugin.version}</version>

                <configuration>
                    <images>
                        <image>
                            <name>${docker.image.name}</name>
                            <alias>${project.artifactId}</alias>
                            <build>
                                <args>
                                    <ARTIFACT_ID>${project.artifactId}</ARTIFACT_ID>
                                    <BUILD_DATE>${docker.build.label.build_date}</BUILD_DATE>
                                    <VCS_URL>${docker.build.label.vcs_url}</VCS_URL>
                                    <VCS_REF>${docker.build.label.vcs_ref}</VCS_REF>
                                    <VENDOR>${docker.build.label.vendor}</VENDOR>
                                    <VERSION>${docker.build.label.version}</VERSION>
                                    <SCHEMA_VERSION>${docker.build.label.schema_version}</SCHEMA_VERSION>
                                </args>
                                <dockerFileDir>${project.basedir}/docker/image</dockerFileDir>
                                <tags>
                                    <tag>latest</tag>
                                </tags>
                            </build>
                            <run>
                                <restartPolicy>
                                    <name>on-failure</name>
                                    <retry>10</retry>
                                </restartPolicy>
                                <namingStrategy>alias</namingStrategy>
                                <network>
                                    <mode>custom</mode>
                                    <name>${docker.network.name}</name>
                                    <alias>${project.artifactId}</alias>
                                </network>
                                <ports>
                                    <port>8080:80</port>
                                </ports>
                            </run>
                        </image>
                    </images>
                </configuration>
                <executions>
                    <execution>
                        <id>docker-build</id>
                        <phase>install</phase>
                        <goals>
                            <goal>build</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <!-- end::docker[] -->
        </plugins>
    </build>
</project>

The POM (Project Object Model) file is the main configuration file for a maven project. For those who aren’t familiar, it’s where you define the project dependencies and build configuration. The important part for our purposes here is the build plugin configuration. You can see we’re making use of the docker-maven-plugin from fabric8. We give our image a name and an alias for the network which we'll discuss later. In the build configuration we have arguments, the location of our Dockerfile, and our image tags. In the run configuration we have our restart policy, our container naming strategy, the network configuration and the any port mappings. In the execution block we bind the docker plugin's build goal to maven's install goal (More on that later).

Dockerfile

FROM library/nginx

ARG ARTIFACT_ID
ARG BUILD_DATE
ARG VCS_URL
ARG VCS_REF
ARG VENDOR
ARG VERSION
ARG SCHEMA_VERSION

LABEL org.label-schema.build-date="${BUILD_DATE}"
LABEL org.label-schema.vcs-url="${VCS_URL}"
LABEL org.label-schema.vcs-ref="${VCS_REF}"
LABEL org.label-schema.vendor="${VENDOR}"
LABEL org.label-schema.version="${VERSION}"
LABEL org.label-schema.schema-version="${SCHEMA_VERSION}"
LABEL org.label-schema.name="${ARTIFACT_ID}"

COPY container/ /

For this example, our Dockerfile is rather simple. We start with a base nginx image. Then, we declare arguments and use them to define labels. These are the same arguments present in the build configuration in the pom file. The last line copies the contents of our container folder to the root directory of the image.

index.html

<html>
    <body>
        <h1>HELLO WORLD</h1>
    </body>
</html>

This wouldn’t be a good example project without a hello world file. So, here it is — a simple html file that will end up in the container at /usr/share/nginx/html , the default location for files to be served up by nginx.

For our simple example, all we need is these three files.

Building the docker image

Since we have bound the build goal of the docker-maven-plugin to maven's install goal, we can build the image by executing the maven wrapper in the project’s root directory like:

./mvnw clean install

This will result in an image being created named nginx-docker-example which matches the name we have defined in our docker plugin configuration in the pom.xml. It will have the tag latest which we also have defined in the pom configuration.

To set values for the labels we defined in the Dockerfile, you have to use a more verbose version of the same command. For example, this is what our maven install script looks like in our Gitlab CI build containers:

mvn clean install $@ -B \\
    -Djava.security.edg=file:/dev/./urandom \\
    -Ddocker.build.label.build_date="${DATE_RFC_3339}" \\
    -Ddocker.build.label.vcs_url="${VCS_URL}" \\
    -Ddocker.build.label.vcs_ref="${VCS_REF}" \\
    -Ddocker.build.label.vendor="${VENDOR}" \\
    -Ddocker.build.label.version="${PROJECT_VERSION}" \\
    -Ddocker.build.label.schema_version="${SCHEMA_VERSION}"

Gitlab defines some of those environment variables, and others we define ourselves earlier in the script. Notice the name of the flags match names defined in the properties section of our pom.xml file. They are later referenced in the args section of the maven-docker-plugin with the ${...} syntax. You could follow a similar pattern to add additional labels.

Running the docker image

The maven-docker-plugin provides a few other goals, one of which is the start command, which we can invoke by executing:

./mvnw docker:start

This will start up a container of our built image with the configuration we defined in the run configuration of our POM file. It will be running with host port 8080 bound to the container port 80. If you navigate to https://localhost:8080 you should see our “Hello World” page.

If you get an error about the local-network not existing, you may have to create it with:

docker network create --attachable local-network

Conclusions

That’s pretty much it! We apply this maven-based project pattern to most of our projects. You may think, “Goodness, that’s a lot of XML”, or “Just make a bash script that executes docker build,” and these are all very valid considerations — however, what’s really important is that there is consistency across your projects. Keeping all of our projects maven-based is how we avoid having many different configurations that require their own, special build processes.

Related Articles

Logging Strategies
5 min read
The Single CLI
3 min read
Using Embedded Mysql in JUnit Tests
7 min read

Back To Top

🎉 You've successfully subscribed to Single Technology!
OK