Mastering Docker and Maven: How to Build Docker Images
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.
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:
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.
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.
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.
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.
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.
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.
The Docker Maven Plugin supports several goals, each corresponding to a specific Docker operation:
These goals can be tied to different Maven build phases, allowing complete control over the Docker lifecycle within the Maven project structure.
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.
Each image configuration in the plugin consists of three main parts:
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.
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.
Before using the Dockerfile Maven Plugin, you need to ensure the following prerequisites are in place:
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.
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.
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.
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.
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
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.
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.
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
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.
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.
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.
To fully understand the Docker Maven Plugin, it’s important to familiarize yourself with some of its core components and concepts:
The Docker Maven Plugin provides several predefined goals that can be tied to Maven phases:
You can associate these goals with Maven build phases, such as package, install, or deploy, to automate Docker operations.
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 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.
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.
The Docker Maven Plugin fits seamlessly into CI/CD pipelines, enabling automatic building and deployment of containerized applications. It allows you to:
Example CI/CD pipeline steps:
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.
Check the Dockerfile path, ensure all build arguments are correctly passed, and verify that the necessary files exist in the project directory.
Ensure the host port is not already in use. Use different ports or stop conflicting services before starting the container.
Run Docker with the necessary privileges. On Linux, add your user to the docker group or use sudo.
Verify that all project dependencies are correctly defined in the pom.xml and that the assembly descriptor includes them.
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.
This plugin is ideal for teams familiar with Docker and those who prefer explicitly managing the Docker image configuration rather than using XML descriptors.
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:
This setup ensures that the Docker container will run securely and efficiently.
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:
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.
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]
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 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.
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.
If you encounter errors during the image build or push:
Most issues can be resolved by reviewing the plugin output logs and checking the Maven build lifecycle phases.
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.
Popular posts
Recent Posts