This is my attempt at creating a smaller Docker image for my Spring Boot application.
Development images
As a starting point, I use the Maven docker image based on Alpine Linux.
FROM maven:3.9.5-eclipse-temurin-21-alpine
RUN mkdir /app
WORKDIR /app
COPY pom.xml ./
RUN mvn dependency:go-offline
COPY docker-entrypoint-dev.sh ./
COPY src ./src
docker-entrypoint-dev.sh
#!/bin/bash
export TERM=xterm
echo "wait 5s"
sleep 5
mvn spring-boot:run -Dspring-boot.run.jvmArguments="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005" &
while true; do
watch -d -t -g "ls -lR . | sha1sum" && mvn compile
done
The above script executes spring-boot:run
, then watch for source code changes, and compiles the Java code.
- The watch command executes a program periodically, showing output fullscreen
- The -d (--difference) option to view the changing output. This option will highlight the changes.
- The -t (--no-title) option is used to turn off the header showing the interval, command, and the current time
- By default, the watch command keeps running until it is interrupted manually by the user (Ctrl+C). However, sometimes instead of highlighting the changes, you’d prefer the watch command to exit when a change is detected completely. We can set watch to exit when the output from the command changes by using the -g (--chgexit) option.
ls -lR . | sha1sum
Produces an sha1sum file source code
Final Image
After building my development image, I want to make the final image for deploying into the server.
To make the image smaller, I use the layering feature and multi-stage build.
Dockerfile
FROM maven:3.9.5-eclipse-temurin-21-alpine AS java-build
WORKDIR /app/
COPY .mvn/ .mvn
COPY pom.xml ./
RUN mvn dependency:go-offline
COPY src src
RUN mvn package
RUN mkdir -p target/dependency && (cd target/dependency; jar -xf ../*.jar)
FROM eclipse-temurin:21-jre-alpine
COPY --from=java-build /etc/passwd /etc/shadow /etc/
ARG DEPENDENCY=/app/target/dependency
# dependencies
COPY --from=java-build ${DEPENDENCY}/BOOT-INF/lib /app/lib
# metadata
COPY --from=java-build ${DEPENDENCY}/META-INF /app/META-INF
# java classes
COPY --from=java-build ${DEPENDENCY}/BOOT-INF/classes /app
ENV _JAVA_OPTIONS "-XX:MaxRAMPercentage=90 -Djava.security.egd=file:/dev/./urandom -Djava.awt.headless=true -Dfile.encoding=UTF-8"
ENTRYPOINT ["java","-cp","app:app/lib/*","com.galapea.techblog.springboot.onlinesurvey.SpringbootOnlineSurveyApplication"]
- First stage: extract the content of our fat Jar.
- Second stage: copy application layers into the final docker image which only contains jre
- Finally, run Java with a classpath definition, instead of a link to a JAR.
After making small code changes, I rebuilt the docker image, the previous layer didn't change java-build 2 to 5/8
, taking advantage of the Docker cache (=> CACHED
).
[+] Building 34.1s (19/19) FINISHED docker:default
=> [survey-app internal] load build definition from Dockerfile 0.1s
=> => transferring dockerfile: 839B 0.0s
=> [survey-app internal] load .dockerignore 0.0s
=> => transferring context: 177B 0.0s
=> [survey-app internal] load metadata for docker.io/library/maven:3.9.5-eclipse-temurin-21-alpine 1.1s
=> [survey-app internal] load metadata for docker.io/library/eclipse-temurin:21-jre-alpine 1.1s
=> [survey-app java-build 1/8] FROM docker.io/library/maven:3.9.5-eclipse-temurin-21-alpine@sha256:c0f2d9ba99a1a3a0c95bdd7b1888fff4183e06fe8c5c3c8cb0c19dc7d1ab893b 0.0s
=> [survey-app stage-1 1/5] FROM docker.io/library/eclipse-temurin:21-jre-alpine@sha256:2a4755c16fe3390e6a89daed9adfc6d9dc7be116dfce84497cf84f761b973311 0.0s
=> [survey-app internal] load build context 0.0s
=> => transferring context: 8.94kB 0.0s
=> CACHED [survey-app java-build 2/8] WORKDIR /app/ 0.0s
=> CACHED [survey-app java-build 3/8] COPY .mvn/ .mvn 0.0s
=> CACHED [survey-app java-build 4/8] COPY pom.xml ./ 0.0s
=> CACHED [survey-app java-build 5/8] RUN mvn dependency:go-offline 0.0s
=> [survey-app java-build 6/8] COPY src src 0.1s
=> [survey-app java-build 7/8] RUN mvn package 30.5s
=> [survey-app java-build 8/8] RUN mkdir -p target/dependency && (cd target/dependency; jar -xf ../*.jar) 1.6s
=> CACHED [survey-app stage-1 2/5] COPY --from=java-build /etc/passwd /etc/shadow /etc/ 0.0s
=> CACHED [survey-app stage-1 3/5] COPY --from=java-build /app/target/dependency/BOOT-INF/lib /app/lib 0.0s
=> CACHED [survey-app stage-1 4/5] COPY --from=java-build /app/target/dependency/META-INF /app/META-INF 0.0s
=> [survey-app stage-1 5/5] COPY --from=java-build /app/target/dependency/BOOT-INF/classes /app 0.1s
=> [survey-app] exporting to image 0.1s
And the final docker image.
In the next iteration, I will try to use Jlink.
Top comments (0)