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.