How to Dockerize Maven Project? and How Many Ways to Accomplish It

How to dockerize maven project? and how many ways to accomplish it?

Working example.

This is not a spring boot tutorial. It's the updated answer to a question on how to run a Maven build within a Docker container.

Question originally posted 4 years ago.

1. Generate an application

Use the spring initializer to generate a demo app

https://start.spring.io/

Sample Image

Extract the zip archive locally

2. Create a Dockerfile

#
# Build stage
#
FROM maven:3.6.0-jdk-11-slim AS build
COPY src /home/app/src
COPY pom.xml /home/app
RUN mvn -f /home/app/pom.xml clean package

#
# Package stage
#
FROM openjdk:11-jre-slim
COPY --from=build /home/app/target/demo-0.0.1-SNAPSHOT.jar /usr/local/lib/demo.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","/usr/local/lib/demo.jar"]

Note

  • This example uses a multi-stage build. The first stage is used to build the code. The second stage only contains the built jar and a JRE to run it (note how jar is copied between stages).

3. Build the image

docker build -t demo .

4. Run the image

$ docker run --rm -it demo:latest

. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.1.3.RELEASE)

2019-02-22 17:18:57.835 INFO 1 --- [ main] com.example.demo.DemoApplication : Starting DemoApplication v0.0.1-SNAPSHOT on f4e67677c9a9 with PID 1 (/usr/local/bin/demo.jar started by root in /)
2019-02-22 17:18:57.837 INFO 1 --- [ main] com.example.demo.DemoApplication : No active profile set, falling back to default profiles: default
2019-02-22 17:18:58.294 INFO 1 --- [ main] com.example.demo.DemoApplication : Started DemoApplication in 0.711 seconds (JVM running for 1.035)

Misc

Read the Docker hub documentation on how the Maven build can be optimized to use a local repository to cache jars.

  • https://hub.docker.com/_/maven


Update (2019-02-07)

This question is now 4 years old and in that time it's fair to say building application using Docker has undergone significant change.

Option 1: Multi-stage build

This new style enables you to create more light-weight images that don't encapsulate your build tools and source code.

The example here again uses the official maven base image to run first stage of the build using a desired version of Maven. The second part of the file defines how the built jar is assembled into the final output image.

FROM maven:3.5-jdk-8 AS build  
COPY src /usr/src/app/src
COPY pom.xml /usr/src/app
RUN mvn -f /usr/src/app/pom.xml clean package

FROM gcr.io/distroless/java
COPY --from=build /usr/src/app/target/helloworld-1.0.0-SNAPSHOT.jar /usr/app/helloworld-1.0.0-SNAPSHOT.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","/usr/app/helloworld-1.0.0-SNAPSHOT.jar"]

Note:

  • I'm using Google's distroless base image, which strives to provide just enough run-time for a java app.

Option 2: Jib

I haven't used this approach but seems worthy of investigation as it enables you to build images without having to create nasty things like Dockerfiles :-)

https://github.com/GoogleContainerTools/jib

The project has a Maven plugin which integrates the packaging of your code directly into your Maven workflow.


Original answer (Included for completeness, but written ages ago)

Try using the new official images, there's one for Maven

https://registry.hub.docker.com/_/maven/

The image can be used to run Maven at build time to create a compiled application or, as in the following examples, to run a Maven build within a container.

Example 1 - Maven running within a container

The following command runs your Maven build inside a container:

docker run -it --rm \
-v "$(pwd)":/opt/maven \
-w /opt/maven \
maven:3.2-jdk-7 \
mvn clean install

Notes:

  • The neat thing about this approach is that all software is installed and running within the container. Only need docker on the host machine.
  • See Dockerfile for this version

Example 2 - Use Nexus to cache files

Run the Nexus container

docker run -d -p 8081:8081 --name nexus sonatype/nexus

Create a "settings.xml" file:

<settings>
<mirrors>
<mirror>
<id>nexus</id>
<mirrorOf>*</mirrorOf>
<url>http://nexus:8081/content/groups/public/</url>
</mirror>
</mirrors>
</settings>

Now run Maven linking to the nexus container, so that dependencies will be cached

docker run -it --rm \
-v "$(pwd)":/opt/maven \
-w /opt/maven \
--link nexus:nexus \
maven:3.2-jdk-7 \
mvn -s settings.xml clean install

Notes:

  • An advantage of running Nexus in the background is that other 3rd party repositories can be managed via the admin URL transparently to the Maven builds running in local containers.

How to write a dockerfile to add maven build on existing spring boot project which contains jdk

I have got few resources before from stackoverflow but I cannot do
experiment, I need code which can run thought the given jdk script
mentioned previously.

If I understand well, your issue is you want to add the maven packaging step in the docker build while still using that exact version of JDK :
FROM openjdk:8-jdk-alpine at runtime and to not depend on that base image : FROM maven:3.6.0-jdk-11-slim AS build.

First thing : don't worry to depend on another base docker image in a Docker Build.

Docker build has a multi-stage build feature since the 17.05 Docker version (05/2017), which allows to define multiple stages in a build and that suits to your need.

Indeed, in the first stage you could rely on the maven docker image to build your application while in the second stage you could rely on the JDK docker image to run your application.

The multi-stage build feature has also another advantage : image size optimization since intermediate layers produced by previous steps are not kept in the final image size.

For integration/production like environment, that is probably the way that I would use.

To come back to your issue, if you want to use the answer that you linked, the main thing that you need to change is the JDK tag of the final base image to match your requirement and you should also adapt some small things.

As a side note, currently you copy the JAR at the root of the image. That is not a good practice for security reasons. You should rather copy it in a folder, for example /app.

When you reference the jar built by maven, it is also preferable to prefix the jar by its artifactId/final name without its version rather than consider any jar in the target folder as the fat jar as you do here : ARG JAR_FILE=target/*.jar.

So it would give :

# Build stage
FROM maven:3.6.0-jdk-11-slim AS build
COPY pom.xml /app/
COPY src /app/src
RUN mvn -f /app/pom.xml clean package

# Run stage
FROM openjdk:8-jdk-alpine # Use your target JDK here !
COPY --from=build /app/target/app*.jar /app/app.jar
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app/app.jar"]

Note that the proposed solution is very simple, is enough in many cases but may not suit to your specific context.

There are really several approaches to handle your question. According to your targeted environment, your requirements are not the same and the strategy will differ.

These requirements may be build speed, final image size optimization, fast feedback for local developers. And all of these are not necessarily compatible.

The Docker build area is really a very large subject. I strongly advise you to study more that to be able to adjust the docker build solution to your real needs.

maven built dockerized project that runs a node application

Based on this, you can either use maven-resources-plugin to replace instances of ${...} with the values set in maven before you build the docker file.

Example:

<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
<executions>
<execution>
<id>filter-dockerfile</id>
<phase>generate-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}</outputDirectory>
<resources>
<resource>
<directory>src/main/docker</directory>
<filtering>true</filtering>
</resource>
</resources>
</configuration>
</execution>
</executions>

This assume your docker file is under src/main/docker/ path. The replaced docker file will be copied on ${project.build.directory} path.

Or based on this comment, you could pass arguments to docker file.

Example:

On your maven docker plugin

<configuration>
<buildArgs>
<artifactId>${project.artifactId}</artifactId>
<groupId>${project.groupId}</groupId>
</buildArgs>
</configuration>

Then access those properties as ARGs on docker file

ARG artifactId
ARG groupId
ENV ARTIFACT_ID=${artifactId} GROUP_ID=${groupId}

Hope this help answer you question.

Dockerizing a Multi-module Maven Application?

I would just use the maven jib plugin instead of hand crafting a Dockerfile. Much easier and it should work with multi module projects too I reckon

How do I build a Spring Boot app (Maven) with a Dockerfile?

The following dockerfile works.

# Build stage
FROM maven:3.6.3-jdk-8-slim AS build
COPY src /home/app/src
COPY pom.xml /home/app
RUN mvn -f /home/app/pom.xml clean test package

# Package stage
FROM openjdk:8-jdk-alpine
COPY --from=build /home/app/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","app.jar"]

How to build a docker container for a Java application

The docker registry hub has a Maven image that can be used to create java containers.

Using this approach the build machine does not need to have either Java or Maven pre-installed, Docker controls the entire build process.

Example

├── Dockerfile
├── pom.xml
└── src
├── main
│   ├── java
│   │   └── org
│   │   └── demo
│   │   └── App.java
│   └── resources
│   └── log4j.properties
└── test
└── java
└── org
└── demo
└── AppTest.java

Image is built as follows:

docker build -t my-maven .

And run as follows:

$ docker run -it --rm my-maven
0 [main] INFO org.demo.App - hello world

Dockerfile

FROM maven:3.3-jdk-8-onbuild
CMD ["java","-jar","/usr/src/app/target/demo-1.0-SNAPSHOT-jar-with-dependencies.jar"]

Update

If you wanted to optimize your image to exclude the source you could create a Dockerfile that only includes the built jar:

FROM java:8
ADD target/demo-1.0-SNAPSHOT-jar-with-dependencies.jar /opt/demo/demo-1.0-SNAPSHOT-jar-with-dependencies.jar
CMD ["java","-jar","/opt/demo/demo-1.0-SNAPSHOT-jar-with-dependencies.jar"]

And build the image in two steps:

docker run -it --rm -w /opt/maven \
-v $PWD:/opt/maven \
-v $HOME/.m2:/root/.m2 \
maven:3.3-jdk-8 \
mvn clean install

docker build -t my-app .

__

Update (2017-07-27)

Docker now has a multi-stage build capability. This enables Docker to build an image containing the build tools but only the runtime dependencies.

The following example demonstrates this concept, note how the jar is copied from target directory of the first build phase

FROM maven:3.3-jdk-8-onbuild 

FROM java:8
COPY --from=0 /usr/src/app/target/demo-1.0-SNAPSHOT.jar /opt/demo.jar
CMD ["java","-jar","/opt/demo.jar"]

Adding Maven build steps into docker file for Java Jar?

The Dockerfile examples in Mark O'Connor's answer to the question you've linked to should be very close to what you need. Those are multistage builds because they have multiple FROMs in them. The first stage uses a base image that already contains maven, so no need to install it: FROM maven:3.6.0-jdk-11-slim AS build.

Your current Dockerfile content then becomes the second stage, but you will need to replace your ADD app.jar ... with a COPY which picks the file out of the build stage. Something like

COPY --from=build /path/to/mvn/output/app.jar \
/opt/jboss/jboss-eap-6.2/standalone/deployments/

The --from=build refers to the AS build from the earlier stage. No other files from the build stage will be copied into the later stage(s), so the final image will consist only of layers from the last FROM onwards.

Editing to add: To address the question of how this prevents another user from needing to do any build and install manually: A new user can do this, instead. Assuming that Dockerfile lives in the root of your git project.

git clone <url> your-app
docker build -t your-img your-app # builds all stages
docker run --rm -it your-img # runs whatever the final stage produced

Docker instruction in Java Maven project to skip tests

I tried putting -Dmaven.test.skip=true but didn't work

It depends how you put it. I would try:

RUN ["mvn", "install", "-Dmaven.test.skip=true"]

Although, do read "Maven skip tests" or "Maven packaging without test (skip tests)".

Run mvn commands as part of the docker file using Entrypoint and/or CMD

According to the documentation:
"If CMD is used to provide default arguments for the ENTRYPOINT instruction, both the CMD and ENTRYPOINT instructions should be specified with the JSON array format."
As such you should rewrite CMD as follow:

CMD ["clean","test","-Dsurefire.suiteXmlFiles=/app/abc.xml"]

You can also parameterize entry-point as a JSON array, as per documentation:
ENTRYPOINT["mvn","clean","test","-Dsurefire.suiteXmlFiles=/app/abc.

But, I suggest you use best-practice with an entrypoint ash-file. This ensures that changing these parameters does not require rewriting of the dockerfile:

  1. create an entrypoint.sh file in the code directory. make it executable. It should read like this:

    #!/bin/sh
    if [ "$#" -ne 1 ]
    then
    FILE="abc.xml"
    else
    FILE=$1
    fi
    mvn clean test -Dsurefire.suiteXmlFiles="/app/$FILE"

  2. replace your entrypoint with ENTRYPOINT["./entrypoint.sh"]

  3. replace your command with CMD["abc.xml"]

PS
you have "WORKDIR /app" twice. this isn't what fails you, but it is redundant, you can get rid of it.



Related Topics



Leave a reply



Submit