Maven with docker

Build a local project on a docker container with the Maven image

docker run -it --rm -v $PWD:/usr/src/my-maven -v "$HOME"/.m2:/root/.m2 -w /usr/src/my-maven maven:3.5.2-jdk-8-alpine mvn install
Some explanations :
--rm to automatically remove the container when it exits
-v $PWD:/usr/src/my-maven to mount the current directory of the host (where the maven project to execute is located) into a directory in the container. Here src/my-maven may or not exist in the container, that is not an issue because it will be automatically created if needed.
-v "$HOME"/.m2:/root/.m2 to mount the m2 repository of the host (local repository) to the actual location of the m2 repository of the container.
That increases the build because it doesn’t require the container to look for dependencies at each build but as downside, it may make the build not fully reproducible between the developers environment.
About the location of the local m2 repository in the host, it may which one used by developer or a distinct.
-w /usr/src/my-maven specifies the working directory of the container to the place where the maven project is located (what we does just before by mounting volume with the maven project).

Build a java application by using a custom settings.xml

The idea is to copy during the image build, the file at the expected place and with the expected name.

FROM maven:3.6-jdk-8 AS maven_build
MAINTAINER David XXX
WORKDIR /build/
 
COPY docker/settings-docker.xml /root/.m2/settings.xml
 
RUN mvn package # Here maven relies on the copied settings.xml file

Cache maven dependencies in the buildkit cache

We need to enable buildkit (front end first line declaration and variable definition) and we could so use the --mount flag such as :

RUN --mount=type=cache,target=/root/.m2/repository mvn clean package

Here target=/root/.m2/repository , target represents the folder located in the image that we want to cache.

Use a Nexus container as mirror to speed up downloads

The idea is defining a nexus container where a specific build or any build may rely on to download maven dependencies. Supposing that we run the nexus container on the 8072 external port, we could so communicate with the container thanks to the bridge network (default) via the gateway + port : http://172.17.0.1:8072.
Here is a settings.xml that would set the mirror at that location :

<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
                      https://maven.apache.org/xsd/settings-1.0.0.xsd">
    <localRepository>/root/.m2/repository</localRepository>
 
    <!--To allow dependency deployments  -->
    <servers>
        <server>
            <id>central</id>
            <username>...</username>
            <password>...</password>
        </server>
    </servers>
 
    <!--To use the nexus proxy repository instead of the default maven public repo  -->
    <mirrors>
        <mirror>
            <id>central</id>
            <name>central</name>
            <url>http://172.17.0.1:8072/repository/my-maven-group</url>
            <mirrorOf>*</mirrorOf>
        </mirror>
    </mirrors>
 
    <!--To allow maven to download dependencies from the nexus proxy repository both for  release and snapshot 
           and also to force maven to lookup/redownload snap each build
   -->
    <profiles>
        <profile>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
            <repositories>
                <repository>
                    <id>nexus-public</id>
                    <name>Nexus Public Repository</name>
                    <url>http://172.17.0.1:8072/repository/my-maven-group</url>
                    <releases>
                        <enabled>true</enabled>
                    </releases>
                    <snapshots>
                        <enabled>true</enabled>
                        <updatePolicy>always</updatePolicy>
                    </snapshots>
                </repository>
            </repositories>
        </profile>
    </profiles>
 
 
</settings>

Multi stage build with maven and spring boot

The goal is reducing the image size : at the end of the build, only data of the last stage are kept.
So we create the java build in the first stage, then in the next stage we use it. It means that the source code and any downloaded dependencies are not kept in the created image.
Note that if you use the build cache (with buildkit feature) for maven dependencies as I show below, the image will not contain that but the builder cache will.

# syntax=docker/dockerfile:experimental
 
#################################################
### BUILD : BUILD THE APPLICATION
#################################################
FROM maven:3.6-jdk-8 AS maven_build
MAINTAINER David XXX
WORKDIR /build/
 
COPY docker/settings-docker.xml /root/.m2/settings.xml
 
COPY pom.xml .
COPY src ./src/
 
RUN --mount=type=cache,target=/root/.m2/repository mvn clean package -U -Dmaven.test.skip 
 
#################################################
### BUILD : CONFIGURE THE APPLICATION
#################################################
FROM openjdk:8-jdk-alpine
 
# ENV required both at image build time and runtime
ENV QUIZZ_BASE_PATH=/usr/quizz
 
RUN mkdir -p $QUIZZ_BASE_PATH
 
RUN echo "copying app binary and libraries ..."
ARG DEPENDENCY=/build/target/dependency
COPY --from=maven_build ${DEPENDENCY}/BOOT-INF/lib /app/lib
COPY --from=maven_build ${DEPENDENCY}/BOOT-INF/classes /app
COPY --from=maven_build ${DEPENDENCY}/META-INF /app/META-INF
 
RUN echo "copying app configuration files ..."
COPY config/application.properties $QUIZZ_BASE_PATH
COPY src/main/resources/logback-spring.xml $QUIZZ_BASE_PATH
 
ENTRYPOINT ["sh", "-c", "java -cp /app:/app/lib/* -Djava.security.egd=file:/dev/./urandom \
             davidhxxx.MySpringBootApp \
         --spring.config.location=$QUIZZ_BASE_PATH/application.properties \
         --logging.config=file:$QUIZZ_BASE_PATH/logback-spring.xml \
         ]

The order of DockerFile statements matter because it orders the layers.
Layers are cached while they don’t change. But when a layer is rebuild, it invalidates all next layers. So to use efficiently the layers cache, as a rule of thumb :
– MOST IMPORTANT : expensive tasks have to be located at a place where they are rebuild as few as possible.
So expensive tasks have to appear as soon as possible.
– for moderated expensive tasks, we want to rebuild them as few as possible. So we order them from the less frequent change to the more frequent change.
To illustrate the first rule (expensive task first), here :

RUN --mount=type=cache,target=/root/.m2/repository mvn clean package -U -Dmaven.test.skip

is the longest operation, so we want to locate the layer associated to the earliest possible. In that way, that prevents any next layers that need to be rebuilt from rebuilding that expensive layer.
Here another example of the rule 1 application while much less significant :

COPY --from=maven_build ${DEPENDENCY}/BOOT-INF/lib /app/lib
COPY --from=maven_build ${DEPENDENCY}/BOOT-INF/classes /app
COPY --from=maven_build ${DEPENDENCY}/META-INF /app/META-INF

Copying the application libraries is the most expensive operation of these 3 COPY tasks because it may contains a lot of files and maybe large files too.
So, we don’t want to copy the libraries from the host to the image at each source code change. That’s why COPY --from=maven_build ${DEPENDENCY}/BOOT-INF/classes /app is located after the libraries COPY task.
But as said, here the rule 1 is not the single reason to order libraries copy first. Here the rule 2 matters more since we perform moderated expensive tasks and for them, the less frequent first rule should be applied : libraries used change much less often than source code, so libraries COPY layer should be the first one.
Whatever, here we have minor differences in terms of build speed improvement (some ms to 1 sec) because we have to few moderated tasks. So don’t focus too much on the rule 2 but if you have much more moderated tasks that finally may cost several seconds in your build.

Ce contenu a été publié dans Non classé. Vous pouvez le mettre en favoris avec ce permalien.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *