Spring Boot Docker Images using JRE base
Tags: java, docker, springboot, container
Published: 2023-11-06 14:10:52

This is my attempt at creating a smaller Docker image for my [Spring Boot application](https://github.com/alifruliarso/spring-boot-online-survey).

## 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`).

```console
[+] 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.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p4hgj64usxuwxaaf5acm.png)

In the next iteration, I will try to use Jlink.