Accelerate Maven Application Builds: Maximizing Efficiency with Docker Volumes for Maven Repository Sharing
Tags: java, maven, docker
Published: 2024-05-19 20:22:30


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.