Issue
I am trying to build a huge docker image in an optimized way by applying the principles of incremental building explained here https://www.docker.com/blog/intro-guide-to-dockerfile-best-practices/ .
Unfortunately each time I run the build command docker restarts building the image from scratch, and so I have to download again all the maven dependencies.
Here is the build command:
docker build \
--build-arg MAVEN_SETTINGS_FILE="${HOME}/.m2/settings.xml" \
--build-arg PROJECT_PATH="." \
--file "${DOCKER_FILE}" \
--tag "${IMAGE_TAG}" \
.
and here te Dockerfile
:
# Global vars (for passing between stages)
ARG MAVEN_SETTINGS_FILE
ARG PROJECT_PATH
ARG APP_FOLDER=/app
# 1st stage
FROM maven:3.6-jdk-11 as build
# Build artifact
ARG APP_FOLDER
ENV APP_FOLDER ${APP_FOLDER}
ARG MAVEN_SETTINGS_FILE
ENV MAVEN_SETTINGS_FILE ${MAVEN_SETTINGS_FILE}
ARG PROJECT_PATH
ENV PROJECT_PATH ${PROJECT_PATH}
WORKDIR ${APP_FOLDER}
ADD ${MAVEN_SETTINGS_FILE} .
ADD ${PROJECT_PATH}/pom.xml .
RUN mvn package -s ${MAVEN_SETTINGS_FILE} -DskipTests
ADD ${PROJECT_PATH}/src src
RUN mvn package -s ${MAVEN_SETTINGS_FILE} -DskipTests
# 2nd stage: build image
FROM openjdk:11-jre-slim
ARG APP_FOLDER
ENV APP_FOLDER ${APP_FOLDER}
ENV TARGET_FOLDER ${APP_FOLDER}/target
WORKDIR ${APP_FOLDER}
# Copy the binary built in the 1st stage
COPY --from=build ${TARGET_FOLDER}/myapp-1.0.0-SNAPSHOT.jar .
EXPOSE 8080
CMD ["java", "-jar", "myapp-1.0.0-SNAPSHOT.jar"]
Thank you so much for any suggestion!
Edit 1 (for @sai)
Hi @sai, I checked the cached layers by docker history
command and I observed that the MVN steps are missing!
IMAGE CREATED CREATED BY SIZE COMMENT
b78b3d09d314 16 minutes ago /bin/sh -c #(nop) CMD ["java" "-Doracle.jdb… 0B
a2db7da187f0 16 minutes ago /bin/sh -c #(nop) EXPOSE 8080 0B
ed8f3dc45017 16 minutes ago /bin/sh -c #(nop) EXPOSE 8081 0B
715f12eba281 16 minutes ago /bin/sh -c #(nop) COPY dir:33e2303bf32b392fc… 58.3MB
4edb35a1b6f6 16 minutes ago /bin/sh -c #(nop) COPY file:0b42aca8bb0ad316… 234kB
56ecbfb34e74 16 minutes ago /bin/sh -c #(nop) COPY dir:8497884af419f408f… 4.59kB
255913b0fc25 16 minutes ago /bin/sh -c #(nop) WORKDIR /app 0B
87c5b4ca34df 16 minutes ago /bin/sh -c #(nop) ENV TARGET_FOLDER=/app/ta… 0B
5f95ab5b8d19 16 minutes ago /bin/sh -c #(nop) ENV APP_FOLDER=/app 0B
da6d51b1d3b8 16 minutes ago /bin/sh -c #(nop) ARG APP_FOLDER 0B
940f48bb6c92 3 days ago /bin/sh -c set -eux; arch="$(dpkg --print-… 142MB
<missing> 3 days ago /bin/sh -c #(nop) ENV JAVA_VERSION=11.0.11+9 0B
<missing> 3 days ago /bin/sh -c #(nop) ENV LANG=C.UTF-8 0B
<missing> 3 days ago /bin/sh -c #(nop) ENV PATH=/usr/local/openj… 0B
<missing> 3 days ago /bin/sh -c { echo '#/bin/sh'; echo 'echo "$J… 27B
<missing> 3 days ago /bin/sh -c #(nop) ENV JAVA_HOME=/usr/local/… 0B
<missing> 3 days ago /bin/sh -c set -eux; apt-get update; apt-g… 8.82MB
<missing> 3 days ago /bin/sh -c #(nop) CMD ["bash"] 0B
<missing> 3 days ago /bin/sh -c #(nop) ADD file:7362e0e50f30ff454… 69.3MB
My opinion is that all the instructions which use a variable to be expanded do not generate a cached layer. Is it right for you too?
Edit 2 (Helidon demo)
I tryed to go digger with Helidon demo, and I observed that it does not show in the history the MVN layer. Conversely it uses cached layers.
Look at the docker file and second build execution log:
# 1st stage, build the app
FROM maven:3.6-jdk-11 as build
WORKDIR /helidon
# Create a first layer to cache the "Maven World" in the local repository.
# Incremental docker builds will always resume after that, unless you update
# the pom
ADD pom.xml .
RUN mvn package -Dmaven.test.skip -Declipselink.weave.skip
# Do the Maven build!
# Incremental docker builds will resume here when you change sources
ADD src src
RUN mvn package -DskipTests
RUN echo "done!"
# 2nd stage, build the runtime image
FROM openjdk:11-jre-slim
WORKDIR /helidon
# Copy the binary built in the 1st stage
COPY --from=build /helidon/target/helidon-quickstart-mp.jar ./
COPY --from=build /helidon/target/libs ./libs
CMD ["java", "-jar", "helidon-quickstart-mp.jar"]
EXPOSE 8080
Sending build context to Docker daemon 43.01kB
Step 1/13 : FROM maven:3.6-jdk-11 as build
---> e23b595c92ad
Step 2/13 : WORKDIR /helidon
---> Using cache
---> 25e45ff1f01d
Step 3/13 : ADD pom.xml .
---> Using cache
---> ec5c0a3ecd2c
Step 4/13 : RUN mvn package -Dmaven.test.skip -Declipselink.weave.skip
---> Using cache
---> a21083c406a0
Step 5/13 : ADD src src
---> Using cache
---> d718f90a4c6d
Step 6/13 : RUN mvn package -DskipTests
---> Using cache
---> ebdb2ff847fd
Step 7/13 : RUN echo "done!"
---> Using cache
---> d9c9f46d0af2
Step 8/13 : FROM openjdk:11-jre-slim
---> 940f48bb6c92
Step 9/13 : WORKDIR /helidon
---> Using cache
---> e0aa150de7c3
Step 10/13 : COPY --from=build /helidon/target/helidon-quickstart-mp.jar ./
---> Using cache
---> 0f64c021b20f
Step 11/13 : COPY --from=build /helidon/target/libs ./libs
---> Using cache
---> d2c6fad54ac1
Step 12/13 : CMD ["java", "-jar", "helidon-quickstart-mp.jar"]
---> Using cache
---> 7de63ada236c
Step 13/13 : EXPOSE 8080
---> Using cache
---> d6a11cd62373
Successfully built d6a11cd62373
Successfully tagged helidon-quickstart-mp:latest
**Edit 3 (issue reason found)
I looked better at my second build execution log and I supposed, the ENV variabiles assignments breaking the caching:
Sending build context to Docker daemon 2.294MB
Step 1/36 : ARG CI_CONTAINER_BUILD_FULL_VERSION
Step 2/36 : ARG CI_CONTAINER_BUILD_PROFILE
Step 3/36 : ARG CI_CONTAINER_BUILD_VERSION
Step 4/36 : ARG CI_CONTAINER_MAVEN_BUILD_OPTIONS
Step 5/36 : ARG CI_CONTAINER_MAVEN_POM_ARTIFACT_ID
Step 6/36 : ARG CI_CONTAINER_MAVEN_POM_GROUP_ID
Step 7/36 : ARG CI_CONTAINER_MAVEN_POM_VERSION
Step 8/36 : ARG CI_CONTAINER_MAVEN_SETTINGS_FILE
Step 9/36 : ARG CI_CONTAINER_PROJECT_PATH
Step 10/36 : ARG APP_FOLDER=/app
Step 11/36 : FROM maven:3.6-jdk-11 as build
---> e23b595c92ad
Step 12/36 : ARG APP_FOLDER
---> Using cache
---> 7418c1e78088
Step 13/36 : ENV APP_FOLDER ${APP_FOLDER}
---> Using cache
---> 9fb55b74b57b
Step 14/36 : ARG CI_CONTAINER_MAVEN_SETTINGS_FILE
---> Using cache
---> 9de7642ea7ae
Step 15/36 : ENV CI_CONTAINER_MAVEN_SETTINGS_FILE ${CI_CONTAINER_MAVEN_SETTINGS_FILE}
---> Running in 0d5655b4dd2b
Removing intermediate container 0d5655b4dd2b
---> d590a3bc4167
Step 16/36 : ARG CI_CONTAINER_MAVEN_BUILD_OPTIONS
---> Running in 528c5d6c82f2
Removing intermediate container 528c5d6c82f2
---> 5ec69a0c4629
Step 17/36 : ENV CI_CONTAINER_MAVEN_BUILD_OPTIONS ${CI_CONTAINER_MAVEN_BUILD_OPTIONS}
---> Running in eefe59ddc94b
Removing intermediate container eefe59ddc94b
---> beb77c7c67e5
Step 18/36 : ARG CI_CONTAINER_PROJECT_PATH
---> Running in da389f2e0824
Removing intermediate container da389f2e0824
---> 6835d33be70e
Step 19/36 : ENV CI_CONTAINER_PROJECT_PATH ${CI_CONTAINER_PROJECT_PATH}
---> Running in 89420b67a110
Removing intermediate container 89420b67a110
---> fd1b05ed1dfd
Step 20/36 : WORKDIR ${APP_FOLDER}
---> Running in 39135509f1d7
Removing intermediate container 39135509f1d7
---> 833258753a45
Step 21/36 : COPY ${CI_CONTAINER_MAVEN_SETTINGS_FILE} .
---> 6f1b6a7176de
Step 22/36 : COPY ${CI_CONTAINER_PROJECT_PATH}/pom.xml .
---> 92f34751c641
Step 23/36 : RUN mvn -e -B -s ${CI_CONTAINER_MAVEN_SETTINGS_FILE} -DskipTests -Dmaven.openapi-generator.skip ${CI_CONTAINER_MAVEN_BUILD_OPTIONS} package
---> Running in 38fcf8af4523
[INFO] Error stacktraces are turned on.
[INFO] Scanning for projects...
[INFO] Downloading from public: http://ci.betting.sisal.it/nexus/repository/maven-public/it/sisal/betting/root-pom/2.0.2/root-pom-2.0.2.pom
[INFO] Downloaded from public: http://ci.betting.sisal.it/nexus/repository/maven-public/it/sisal/betting/root-pom/2.0.2/root-pom-2.0.2.pom (12 kB at 41 kB/s)
[INFO] Downloading from public: http://ci.betting.sisal.it/nexus/repository/maven-public/io/helidon/helidon-dependencies/2.3.0/helidon-dependencies-2.3.0.pom
... and so on...
Now I should know to make variable assignments cacheable!!!
Solution
I found the solution!
The first problem was related to a detail of my build command that I did not show in the original post: the MAVEN_SETTINGS_FILE
was generated by the mktemp
so it used to change at any execution, consequently invalidating the following layers.
The second issue was that I did not really need the ENV variables, but the ARGs were enough.
So, I rewritten them as follow, and all the layers are reused from cache as expected.
M2_SETTINGS_FILE=.settings.xml
cp "${HOME}/.m2/settings.xml" "${M2_SETTINGS_FILE}"
docker build \
--build-arg MAVEN_SETTINGS_FILE="${M2_SETTINGS_FILE}" \
--build-arg PROJECT_PATH="." \
--file "${DOCKER_FILE}" \
--tag "${IMAGE_TAG}" \
.
# Global vars (for passing between stages)
ARG MAVEN_SETTINGS_FILE
ARG PROJECT_PATH
ARG APP_FOLDER=/app
# 1st stage
FROM maven:3.6-jdk-11 as build
# Build artifact
ARG APP_FOLDER
ARG MAVEN_SETTINGS_FILE
ARG PROJECT_PATH
WORKDIR ${APP_FOLDER}
ADD ${MAVEN_SETTINGS_FILE} .
ADD ${PROJECT_PATH}/pom.xml .
RUN mvn package -s ${MAVEN_SETTINGS_FILE} -DskipTests
ADD ${PROJECT_PATH}/src src
RUN mvn package -s ${MAVEN_SETTINGS_FILE} -DskipTests
# 2nd stage: build image
FROM openjdk:11-jre-slim
ARG APP_FOLDER
ARG TARGET_FOLDER ${APP_FOLDER}/target
WORKDIR ${APP_FOLDER}
# Copy the binary built in the 1st stage
COPY --from=build ${TARGET_FOLDER}/myapp-1.0.0-SNAPSHOT.jar .
EXPOSE 8080
CMD ["java", "-jar", "myapp-1.0.0-SNAPSHOT.jar"]
Answered By - Antonio Petricca