Mastering Docker and Maven: How to Build Docker Images

Introduction to Maven and Docker

Understanding Maven

Maven is a comprehensive software project management and build automation tool. It is primarily designed to manage a project’s build, reporting, and documentation from a centralized piece of information known as the Project Object Model (POM). The POM file, typically named pom.xml, contains all configuration details required by Maven to build a project, such as dependencies, plugins, and project-specific parameters.

Maven simplifies the build process, promotes uniform builds, and allows developers to manage a project’s lifecycle through defined phases. These phases include validation, compilation, testing, packaging, installation, and deployment. By adhering to conventions and standard structures, Maven helps developers automate and streamline many repetitive tasks within the software development lifecycle.

Maven also offers extensive plugin capabilities, which extend its functionality. These plugins help in executing goals like compiling code, generating documentation, packaging artifacts, and even deploying the output to various environments.

Key Concepts in Maven

One of the fundamental concepts in Maven is its lifecycle. The build lifecycle comprises different phases, and each phase represents a stage in the build process. Some of the core phases include:

  • Validate: Check if the project is correct and all necessary information is available.

  • Compile: Compile the source code.

  • Test: Run unit tests.

  • Package: Package the compiled code into a distributable format like JAR or WAR.

  • Install: Install the package into the local repository.

  • Deploy: Copy the final package to a remote repository for sharing with other developers.

Maven also supports dependency management, where dependencies are external libraries or modules required by your project. These dependencies are managed through repositories, and Maven can automatically download them as needed.

Introducing Docker

Docker is a platform that enables developers to build, deploy, and manage applications using containerization. Containers are lightweight, portable, and self-sufficient units that include everything needed to run a piece of software: the code, runtime, libraries, and system tools.

Docker simplifies application deployment by eliminating inconsistencies between different environments. It helps in creating reproducible environments for development, testing, and production. Each Docker container runs in isolation, which ensures security and consistency across different stages of software deployment.

A Docker image is a read-only template that contains a set of instructions to create a container. These images are created using Dockerfiles, which contain a sequence of commands to install and configure software inside the image. Once an image is built, it can be run as a container.

Integrating Maven with Docker

Using Maven together with Docker provides a powerful way to manage the complete build and deployment lifecycle of an application. Maven handles the building and dependency management, while Docker encapsulates the application in a portable container.

To run a Maven project inside a Docker container, you can use a Maven Docker image. These are pre-configured Docker images with Maven and a Java Development Kit (JDK) installed. The following command shows how to run a Maven build inside a Docker container:

Docker run -it– rm– name my-maven-project -v “$(pwd)”:/usr/src/mymaven -w /usr/src/mymaven maven:3.3-jdk-8 mvn clean install

 

This command mounts the current working directory to /usr/src/mymaven inside the container and runs the mvn clean install command. The -rm flag ensures that the container is removed after the command completes.

This approach allows for consistent builds across different environments, ensuring that the application behaves the same way on any developer machine or server.

Building Docker Images with Custom Configuration

Basics of Docker Image Creation

A Docker image can be considered the foundation for a Docker container. It includes the application and all its dependencies, libraries, and environment settings. Docker images are created using Dockerfiles, which are plain-text files containing a list of instructions that specify how to build the image.

The process of creating a Docker image involves defining a base image, installing necessary packages, copying files, exposing ports, and defining entry points. Here is an example command to build a Docker image:

docker build –tag my_local_maven:3.5.2-jdk-8 .

 

This command builds a Docker image with the tag my_local_maven:3.5.2-jdk-8 using the Dockerfile located in the current directory.

Adding Custom Packages to Dockerfile

When you need a customized environment, you can extend an existing base image by adding custom packages and configurations. This is done through the Dockerfile, where you can include RUN, COPY, and other Docker instructions to customize the image.

For example:

FROM maven:3.6.3-jdk-8

RUN apt-get update && apt-get install -y some-package

COPY. /app

WORKDIR /app

RUN mvn clean install

 

This Dockerfile starts from a Maven base image, installs additional packages, copies the application files into the image, sets the working directory, and builds the project using Maven.

Managing Docker with Docker Maven Plugin

The Docker Maven Plugin is a useful tool for managing Docker containers and images through Maven. It allows developers to define Docker-related tasks directly within the pom.xml file of a Maven project. This integration helps in automating Docker image creation, container start and stop, and other related operations.

With this plugin, you can execute Docker commands as part of the Maven build lifecycle, making the process more integrated and automated.

Predefined Goals of Docker Maven Plugin

The Docker Maven Plugin supports several goals, each corresponding to a specific Docker operation:

  • Docker:start: Creates and starts Docker containers.

  • Docker:stop: Stops and removes running containers.

  • Docker:build: Builds Docker images.

  • Docker:push: Pushes Docker images to a registry.

  • Docker:remove: Removes Docker images from the local Docker host.

  • Docker:logs: Displays container logs.

These goals can be tied to different Maven build phases, allowing complete control over the Docker lifecycle within the Maven project structure.

Configuring Docker Maven Plugin in pom.xml

To use the Docker Maven Plugin, you need to add the plugin configuration to your pom.xml. Below is a sample configuration:

<plugin>

    <groupId>org.jolokia</groupId>

    <artifactId>docker-maven-plugin</artifactId>

    <version>0.11.5</version>

    <configuration>

        <images>

            <image>

                <alias>user</alias>

                <name>shivamchandra/javaee7-docker-maven</name>

                <build>

                    <from>shivamchandra/wildfly:8.2</from>

                    <assembly>

                        <descriptor>assembly.xml</descriptor>

                        <basedir>/</basedir>

                    </assembly>

                    <ports>

                        <port>8080</port>

                    </ports>

                </build>

                <run>

                    <ports>

                        <port>8080:8080</port>

                    </ports>

                </run>

            </image>

        </images>

    </configuration>

    <executions>

        <execution>

            <id>docker:build</id>

            <phase>package</phase>

            <goals>

                <goal>build</goal>

            </goals>

        </execution>

        <execution>

            <id>docker:start</id>

            <phase>install</phase>

            <goals>

                <goal>start</goal>

            </goals>

        </execution>

    </executions>

</plugin>

 

This configuration defines an image with a base image, port mappings, and assembly descriptors. The docker: build goal is bound to the package phase, and the docker: start goal is bound to the install phase.

Understanding the Image Configuration Structure

Each image configuration in the plugin consists of three main parts:

  • The image name and alias

  • The build section, which defines how the image is built

  • The run section, which describes how the container should run

In the build section, you can specify the base image, artifacts to be included, and ports to expose. The artifacts are usually defined using an assembly descriptor, which is an XML file located in src/main/docker.

An example assembly.xml might look like this:

<assembly>

   <id>javaee7-docker-maven</id>

   <dependencySets>

     <dependencySet>

       <includes>

         <include>org.javaee7.sample:javaee7-docker-maven</include>

       </includes>

       <outputDirectory>/opt/jboss/wildfly/standalone/deployments/</outputDirectory>

       <outputFileNameMapping>javaee7-docker-maven.war</outputFileNameMapping>

     </dependencySet>

   </dependencySets>

</assembly>

 

The run section is used to configure how the container should be executed. You can define port mappings and other runtime settings here.

Building Docker Images with Spotify Dockerfile Maven Plugin

Overview of Spotify Dockerfile Maven Plugin

The Dockerfile Maven Plugin provided by Spotify allows developers to integrate Docker image creation directly into the Maven lifecycle. This plugin offers an efficient way to build Docker images by specifying a Dockerfile and relevant build arguments in the pom.xml file. It is a useful tool for Java developers looking to containerize applications without leaving the Maven build environment.

By using this plugin, you can define build steps, specify the Dockerfile path, and pass custom arguments during the image build. It simplifies the process and ensures consistency across environments.

Prerequisites for Using the Plugin

Before using the Dockerfile Maven Plugin, you need to ensure the following prerequisites are in place:

  • Docker must be installed and configured on your machine.

  • A Dockerfile should exist in the root directory of your Maven project.

  • The pom.xml must be configured with the necessary plugin settings.

Sample Dockerfile for a Java Application

Here is a simple Dockerfile that defines how to build a Docker image for a Java application:

FROM adoptopenjdk/openjdk11:alpine

RUN addgroup -S spring && adduser -S spring -G spring

USER spring: spring

VOLUME /tmp

ARG JAR_FILE

ADD ${JAR_FILE} /app/app.jar

EXPOSE 8080

ENTRYPOINT [“java”,”-Djava.security.egd=file:/dev/./urandom”,”-jar”,”/app/app.jar”]

 

This Dockerfile uses an OpenJDK base image, creates a new user and group, sets a working directory, and adds the application JAR file to the image. It also exposes port 8080 and sets the entry point to run the JAR file.

Configuring the Plugin in pom.xml

In your pom.xml, you need to include the plugin configuration to build and push the Docker image using the Spotify Dockerfile Maven Plugin. Below is an example configuration:

<plugin>

    <groupId>com.spotify</groupId>

    <artifactId>dockerfile-maven-plugin</artifactId>

    <version>1.4.13</version>

    <executions>

        <execution>

            <id>default</id>

            <goals>

                <goal>build</goal>

                <goal>push</goal>

            </goals>

        </execution>

    </executions>

    <configuration>

        <repository>${project.artifactId}</repository>

        <tag>${project.version}</tag>

        <buildArgs>

            <JAR_FILE>target/${project.build.finalName}.jar</JAR_FILE>

        </buildArgs>

    </configuration>

</plugin>

 

This configuration defines the Docker image repository and tag based on the Maven project values and sets the JAR file as a build argument. The build and push goals ensure the Docker image is built and optionally pushed to a Docker registry during the Maven lifecycle.

Running the Maven Build

Once the Dockerfile and plugin configuration are in place, you can build the Docker image using the following command:

mvn package

 

This command packages the Java application and then uses the Dockerfile Maven Plugin to build the Docker image. If configured, it will also push the image to the specified repository.

After successful execution, you should see confirmation messages indicating that the image was built and optionally pushed.

Best Practices for Docker Maven Integration

When writing a Dockerfile, aim to keep it simple and efficient. Use minimal base images, combine RUN commands to reduce image layers, and clean up unnecessary files to minimize image size. This results in faster builds and better performance.

For example:

RUN apt-get update && apt-get install -y package-name && rm -rf /var/lib/apt/lists/*

 

This line updates the package manager, installs the required package, and removes cached files to keep the image clean.

Use .dockerignore File

Similar to .gitignore, a .dockerignore file helps prevent unnecessary files from being included in the Docker image. This improves build performance and security by excluding files like logs, temporary files, and build artifacts that are not needed in the image.

Sample .dockerignore:

target/

*.log

*.tmp

.idea/

*.iml

 

Leverage Multi-Stage Builds

Multi-stage builds help reduce the final image size by separating the build environment from the runtime environment. In this approach, one stage is used to compile the application, and another stage is used to run the compiled artifact.

Example:

FROM maven:3.8.1-jdk-11 AS builder

COPY. /app

WORKDIR /app

RUN mvn clean package

 

FROM adoptopenjdk/openjdk11:alpine

COPY –from=builder /app/target/app.jar /app/app.jar

ENTRYPOINT [“java”,”-jar”,”/app/app.jar”]

 

This strategy ensures that only the final executable is included in the runtime image, significantly reducing its size and potential attack surface.

Automate Image Tagging

Use dynamic tagging in your Maven configuration to automate versioning. This helps track builds and enables better image management.

In your pom.xml, you can use:

<tag>${project.version}</tag>

 

This assigns the project version as the Docker image tag, ensuring consistency between application versions and container images.

Running Docker Containers from Maven Builds

Starting and Stopping Containers

After building the Docker image, you can run a container from it using the docker run command. This can also be automated using the Docker Maven Plugin, as previously described.

Manual example:

dDockerrun -d -p 8080:8080– name my-app-container myapp:1.0

 

This command starts a detached container from the image and maps port 8080 of the container to the host.

To stop and remove the container:

docker stop my-app-container

docker rm my-app-container

 

Viewing Logs

To view logs from the running container, use:

Docker logs my-app-container

 

You can monitor real-time logs by adding the -f flag:

Docker logs -f my-app-container

 

This is particularly helpful during development and debugging to track application behavior.

Binding Volumes and Environment Variables

When running containers, you may need to bind volumes or set environment variables. These can be added to the Docker run command or defined in the Maven plugin configuration.

Example:

docker run -d -p 8080:8080 -e ENV_VAR=value -v /host/path:/container/path myapp:1.0

 

Environment variables and mounted volumes help in configuring the container without altering the image, making the containers more flexible and reusable.

Docker Maven Plugin Deep Dive

Introduction to Docker Maven Plugin

The Docker Maven Plugin is a powerful tool that integrates Docker with Maven to streamline the process of building, managing, and deploying Docker containers directly from Maven projects. It allows developers to define Docker-related tasks within the pom.xml file, automating image creation, container operations, and deployment workflows.

This plugin supports operations such as building Docker images, starting and stopping containers, removing images, and pushing images to Docker registries. Its integration into Maven enables a seamless development and deployment experience, making it an essential tool for DevOps and CI/CD pipelines.

Core Concepts and Terminology

To fully understand the Docker Maven Plugin, it’s important to familiarize yourself with some of its core components and concepts:

  • Image: A snapshot of the application and its environment, used to create containers.

  • Container: A running instance of an image, isolated from the host system.

  • Build: The process of creating a Docker image.

  • Run: The process of starting a container from a Docker image.

  • Push: Uploading a Docker image to a registry.

  • Remove: Deleting Docker images or containers.

Plugin Goals and Phases

The Docker Maven Plugin provides several predefined goals that can be tied to Maven phases:

  • Docker:build: Builds the Docker image.

  • Docker:start: Starts a container from the image.

  • Docker:stop: Stops the running container.

  • Docker:push: Pushes the image to a remote registry.

  • Docker:remove: Deletes the local image.

You can associate these goals with Maven build phases, such as package, install, or deploy, to automate Docker operations.

Sample Configuration of Docker Maven Plugin

Below is an example configuration of the plugin in a Maven pom.xml file:

<plugin>

    <groupId>org.jolokia</groupId>

    <artifactId>docker-maven-plugin</artifactId>

    <version>0.11.5</version>

    <configuration>

        <images>

            <image>

                <alias>user</alias>

                <name>mycompany/java-app</name>

                <build>

                    <from>openjdk:11-jre-slim</from>

                    <assembly>

                        <descriptor>assembly.xml</descriptor>

                        <basedir>/</basedir>

                    </assembly>

                    <ports>

                        <port>8080</port>

                    </ports>

                </build>

                <run>

                    <ports>

                        <port>8080:8080</port>

                    </ports>

                </run>

            </image>

        </images>

    </configuration>

    <executions>

        <execution>

            <id>docker:build</id>

            <phase>package</phase>

            <goals>

                <goal>build</goal>

            </goals>

        </execution>

        <execution>

            <id>docker:start</id>

            <phase>install</phase>

            <goals>

                <goal>start</goal>

            </goals>

        </execution>

    </executions>

</plugin>

 

Assembly Descriptors

Assembly descriptors are XML files used to define which files and directories should be included in the Docker image. They are usually stored in the src/main/docker directory and referenced in the plugin configuration.

Example assembly.xml:

<assembly xmlns=”http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3″

          xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”

          xsi:schemaLocation=”http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd”>

    <id>java-docker</id>

    <dependencySets>

        <dependencySet>

            <includes>

                <include>com.example:myapp</include>

            </includes>

            <outputDirectory>/app</outputDirectory>

            <outputFileNameMapping>myapp.jar</outputFileNameMapping>

        </dependencySet>

    </dependencySets>

</assembly>

 

This descriptor includes the built JAR file into the image at the /app directory.

Run Configuration

The run section in the plugin configuration defines how the container will be executed. You can specify ports to expose, environment variables, and other runtime options.

Example:

<run>

    <ports>

        <port>8080:8080</port>

    </ports>

    <env>

        <JAVA_OPTS>-Xmx512m</JAVA_OPTS>

    </env>

</run>

 

This configuration maps port 8080 and sets a JVM option for memory allocation.

Integrating with CI/CD Pipelines

The Docker Maven Plugin fits seamlessly into CI/CD pipelines, enabling automatic building and deployment of containerized applications. It allows you to:

  • Build images during the package phase

  • Start containers during install or verify.

  • Push images to a registry during deployment.

Example CI/CD pipeline steps:

  • Checkout code

  • Run mvn clean package.

  • Automatically build a Docker image.

  • Push image to a Docker registry.

  • Deploy the container to staging or production.

Handling Multiple Services

For multi-module Maven projects or microservice architectures, you can configure multiple Docker images in a single pom.xml or across different modules. Each image can have its build and run settings.

Example:

<images>

    <image>

        <name>service1</name>

        <build>…</build>

    </image>

    <image>

        <name>service2</name>

        <build>…</build>

    </image>

</images>

 

This structure supports scalable Docker deployments managed entirely from Maven.

Troubleshooting Common Issues

Image Build Failures

Check the Dockerfile path, ensure all build arguments are correctly passed, and verify that the necessary files exist in the project directory.

Port Binding Conflicts

Ensure the host port is not already in use. Use different ports or stop conflicting services before starting the container.

Permissions Errors

Run Docker with the necessary privileges. On Linux, add your user to the docker group or use sudo.

Missing Dependencies

Verify that all project dependencies are correctly defined in the pom.xml and that the assembly descriptor includes them.

Building Docker Images Using the Dockerfile Maven Plugin

Introduction to the Dockerfile Maven Plugin

The Dockerfile Maven Plugin is another effective tool that enables the creation of Docker images directly from Maven builds. Unlike the Docker-maven-plugin, which uses descriptors and structured configuration, the Dockerfile Maven Plugin uses a standard Dockerfile to define how the image is built. This approach provides more flexibility and aligns closely with how Docker is used natively.

One of the most popular implementations of this plugin is the Dockerfile-maven-plugin from Spotify. It integrates Docker image creation and pushing into Maven’s lifecycle, allowing Java applications to be containerized in a standardized and maintainable manner.

Benefits of Using the Dockerfile Maven Plugin

  • Enables using a familiar Dockerfile for defining image structure

  • Simplifies integration into Maven-based Java projects

  • Supports multi-stage builds and Docker ARGs

  • Makes CI/CD pipelines more predictable

  • Allows greater control over image creation

This plugin is ideal for teams familiar with Docker and those who prefer explicitly managing the Docker image configuration rather than using XML descriptors.

Writing a Dockerfile for Java Applications

To begin, create a Dockerfile at the root of your Maven project. Below is a basic Dockerfile example for a Spring Boot application:

FROM eclipse-temurin:17-jdk-alpine

 

RUN addgroup -S spring && adduser -S spring -G spring

 

USER spring: spring

 

VOLUME /tmp

 

ARG JAR_FILE

 

ADD ${JAR_FILE} /app/app.jar

 

EXPOSE 8080

 

ENTRYPOINT [“java”, “-Djava.security.egd=file:/dev/./urandom”, “-jar”, “/app/app.jar”]

 

This Dockerfile does the following:

  • Starts from a lightweight JDK base image

  • Adds a user for running the application

  • Defines a volume for temporary files

  • Uses a build argument (JAR_FILE) to inject the application JAR

  • Exposes port 8080

  • Sets the entry point to run the Java application

This setup ensures that the Docker container will run securely and efficiently.

Configuring the Plugin in pom.xml

After creating the Dockerfile, the next step is to configure the Spotify Dockerfile Maven Plugin in your pom.xml file:

<plugin>

    <groupId>com.spotify</groupId>

    <artifactId>dockerfile-maven-plugin</artifactId>

    <version>1.4.13</version>

    <executions>

        <execution>

            <id>default</id>

            <goals>

                <goal>build</goal>

                <goal>push</goal>

            </goals>

        </execution>

    </executions>

    <configuration>

        <repository>${project.artifactId}</repository>

        <tag>${project.version}</tag>

        <buildArgs>

            <JAR_FILE>target/${project.build.finalName}.jar</JAR_FILE>

        </buildArgs>

    </configuration>

</plugin>

 

Key aspects of the configuration:

  • Repository: Sets the name of the Docker image

  • tag: Sets the image tag (usually the project version)

  • buildArgs: Maps the build argument in Dockerfile to the actual JAR path

Building the Docker Image

To build the image using the plugin, use the Maven package phase:

mvn clean package

 

During this phase, Maven will compile the Java application and generate the JAR file. The plugin will then pass this file to the Docker build context using the defined argument and create the Docker image.

Upon successful completion, you will see output confirming the creation of the image with its repository and tag.

Running the Docker Image

Once the image has been built, you can run it using the docker run command:

Docker run -p 8080:8080 your-artifact-id:your-version

 

This command starts the container, maps the internal port to the host, and runs your Java application inside Docker.

You can check logs with:

docker logs [container_id]

 

And stop the container with:

docker stop [container_id]

 

Pushing the Image to a Registry

To push your Docker image to a remote registry, make sure you are logged into your Docker Hub or private registry:

docker login

 

Then tag your image accordingly:

Docker tag your-artifact-id:your-version yourusername/your-repo:your-version

 

And push it:

Docker push yourusername/your-repo:your-version

 

If configured in the plugin, the push goal can also be executed during Maven build phases, automating this step.

Multi-Stage Builds for Optimization

Multi-stage builds help reduce the final image size by separating the build environment from the runtime environment. Here’s how you can modify your Dockerfile to implement it:

FROM maven:3.8.6-openjdk-17 AS build

COPY pom.xml.

COPY src ./src

RUN mvn clean package -DskipTests

 

FROM eclipse-temurin:17-jdk-alpine

COPY –from=build target/app.jar /app/app.jar

ENTRYPOINT [“java”, “-jar”, “/app/app.jar”]

 

This approach compiles the application in the first stage and copies only the final artifact to the second stage, resulting in a smaller image.

Automating With GitHub Actions or Jenkins

You can incorporate the Dockerfile Maven Plugin into CI/CD tools like GitHub Actions or Jenkins:

GitHub Actions Example:

Jobs:

  Build:

    runs-on: ubuntu-latest

    Steps:

      – uses: actions/checkout@v2

      – name: Set up JDK

        uses: actions/setup-java@v2

        with:

          java-version: ’17’

      – name: Build with Maven

        run: mvn clean package

      – name: Login to Docker Hub

        run: echo “${{ secrets.DOCKER_PASSWORD }}” | docker login -u “${{ secrets.DOCKER_USERNAME }}” –password-stdin

      – name: Push Docker Image

        run: mvn dockerfile:build dockerfile:push

 

This pipeline checks out the code, builds the project and Docker image, and pushes it to Docker Hub.

Handling Build and Push Failures

If you encounter errors during the image build or push:

  • Ensure Docker is running and accessible

  • Verify credentials for the Docker registry.

  • Confirm that the JAR_FILE path is correct.t

  • Check that your Dockerfile is located in the project root.ot

Most issues can be resolved by reviewing the plugin output logs and checking the Maven build lifecycle phases.

Best Practices

  • Always use lightweight base images

  • Clean up unnecessary files to reduce image size

  • Use .dockerignore to prevent sending large directories to the Docker daemon.

  • Tag images consistently using semantic versioning.g

  • Sign images if deploying to production eenvironments

Summary

The Dockerfile Maven Plugin is a flexible and powerful approach for Dockerizing Maven-based Java projects. It enables the use of standard Dockerfile practices within Maven’s structure and allows seamless integration into build and deployment pipelines. By understanding its configuration and capabilities, developers can build robust, maintainable Docker workflows for Java applications.

 

img