In today's software development landscape, dockerization has become a fundamental requirement for every project. It eliminates the notorious "it works on my laptop" dilemma, promotes seamless collaboration within teams, and enables hassle-free deployment across various environments using tools like Docker Compose. Our goal is to achieve all of this while maintaining an easy-to-use and highly efficient development environment.
Now, let's dive into dockerizing a Java Maven application and explore the challenges we face. Traditionally, developers often rely on using a Maven image as the base image for their Docker setup, such as the popular Maven 3.6.0 with JDK 8 on Alpine Linux:
Then we want to dockerize a Java Maven application.
We should to be able of
build:
docker build -t my-java-app .
run:
docker run my-java-app
One of the prevalent approaches is to have a maven image as a docker base image.
FROM maven:3.6.0-jdk-8-alpine
include the pom
COPY pom.xml .
include the code
COPY src ./src
build the app
mvn package
However, this approach poses a significant drawback. Every time we execute the docker build command, Maven will re-download the project's dependencies and rebuild the application from scratch if there are any changes in the dependency list or the source code within the src directory. This process is not only inefficient but also tedious and painfully slow.
But fear not! There's a better way to handle this.
By executing the dependency downloads inside the container and leveraging the power of Docker volumes, we can share the Maven repository between the host and the container. Here's how we can achieve it:
We can do it better.
Execute the download dependencies inside the container and share with
docker volume the maven repository between host and container.
- Yes, Inherit from maven
FROM maven:3.6.0-jdk-8-alpine
- Copy pom and code
COPY pom.xml .
COPY src ./src
- Use docker entrypoint.sh pattern
COPY docker-entrypoint.sh /docker-entrypoint.sh
Note: this file must be created first in the root of the app and must have execution permissions.
- Code the docker-entrypoint.sh file.
#!/bin/bash -e
mvn package
exec $@
- Include it as a docker entrypoint
ENTRYPOINT ["/docker-entrypoint.sh"]
By following this approach, we can significantly improve the efficiency of our Docker builds. The Maven dependencies will be downloaded and stored within the Docker volume, eliminating the need for repetitive downloads. Consequently, subsequent builds will be faster, more streamlined, and less error-prone.
Embracing Docker not only simplifies the development and deployment process but also ensures consistent and reliable results across different environments. With these techniques in place, you can confidently build, run, and maintain your Java Maven application within a Dockerized environment.
Following whole example step by step
Create app
run $:
mvn archetype:generate -DgroupId=dev.example -DartifactId=maven-docker-article -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
Update pom.xml
<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>dev.example</groupId>
<artifactId>maven-docker-article</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>maven-docker-article</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<archive>
<manifest>
<mainClass>dev.example.App</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Add some code
package dev.example;
public class App
{
public static void main( String[] args )
{
System.out.println( "This app works and build fast using docker entrypoint and m2 repository volumne sharing" );
}
}
Create Dockerfile
FROM maven:3.6.0-jdk-11 AS build
WORKDIR /app
COPY pom.xml .
COPY src ./src
COPY docker-entrypoint.sh /docker-entrypoint.sh
ENTRYPOINT ["/docker-entrypoint.sh"]
Create docker-entrypoint.sh file.
#!/bin/bash -e
env
mvn package
exec $@
Add execution permissions
chmod +x docker-entrypoint.sh
Build the image
docker build -t my-maven-java-app .
Execute the container/application
docker run --rm -v maven-cache:/root/.m2 maven-dev-article "java -jar /app/target/maven-docker-article-1.0-SNAPSHOT.jar"
With this command we are executing the java application inside its container and leveraging the maven repository shared by volume our docker volume maven-cache and the container maven repository /root/.m2
From now on you docker have a volume named maven-cache that can be used for whatever other maven app you want to dockerize using this same pattern.
Top comments (4)
Why using ancient old Maven version in the container
3.6.0-jdk-8-alpine
... better use most recent versions 3.9.6....Uff. This is just the version I was using in the real project.
Would make any change regarding this article using most recent version?
Thanks 😉
First newer versions are faster and using less memory ... furthermore you could use things like maven.apache.org/extensions/maven-... (Maven 3.9.X+) ... also I would suggest not to build inside the container... Build outside and deploy the resulting jar into the container... also multi layered container builds ... etc....
Not to mentioned things like using Java 8 (source/target) is a bit out of date... use more recent JDK versions like JDK21+ also for support containers...
My new post for Java developers ✨
Hope this help. Thank you 🌻
Top 10 GitHub repositories for Java developers ✨
Sáng Minh Trần ・ May 26