Add docker-images: one Spring Boot 4 service packaged nine ways
Companion code for "Dockerizing Spring Boot 4: Layered Jars, Buildpacks, Distroless and Image Size Benchmarks". Fat jar on JDK and JRE, layered jar on Debian, Alpine and distroless, jlink, the JDK 25 AOT cache, Paketo buildpacks and Jib, each measured for size on disk and pushed, rebuild delta, startup, user and shell. Also PID 1 and signal handling, the jdeps module gap, AOT cache mismatches and buildpacks memory calculation. Transcripts in docs/output/, regenerated by scripts/run-all.sh. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01C3TETMrqVUWeFkNtz3Jbo3
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
# 7. Layered plus a JDK 25 AOT cache (JEP 483/514/515), built by a training run at image build time.
|
||||
# Faster start - and one more large layer that changes on every code change (docs/06-aot-cache.md).
|
||||
FROM eclipse-temurin:25-jre AS builder
|
||||
WORKDIR /builder
|
||||
COPY target/app.jar application.jar
|
||||
RUN java -Djarmode=tools -jar application.jar extract --layers --destination extracted
|
||||
|
||||
FROM eclipse-temurin:25-jre
|
||||
WORKDIR /application
|
||||
COPY --from=builder /builder/extracted/dependencies/ ./
|
||||
COPY --from=builder /builder/extracted/spring-boot-loader/ ./
|
||||
COPY --from=builder /builder/extracted/snapshot-dependencies/ ./
|
||||
COPY --from=builder /builder/extracted/application/ ./
|
||||
RUN java -XX:AOTCacheOutput=app.aot -Dspring.context.exit=onRefresh -jar application.jar
|
||||
ENTRYPOINT ["java", "-XX:AOTCache=app.aot", "-jar", "application.jar"]
|
||||
@@ -0,0 +1,9 @@
|
||||
# 7b. A trap: the AOT cache from image 7, run by a different JVM build (distroless ships Temurin
|
||||
# 25.0.4.1, the cache was trained on 25.0.4). What happens when a base-image bump changes the JVM
|
||||
# under a cache built earlier - measured in docs/06-aot-cache.md.
|
||||
FROM sbd/docker-images:aot-cache AS trained
|
||||
|
||||
FROM gcr.io/distroless/java25-debian13:nonroot
|
||||
WORKDIR /application
|
||||
COPY --from=trained /application/ ./
|
||||
ENTRYPOINT ["java", "-XX:AOTCache=app.aot", "-jar", "application.jar"]
|
||||
@@ -0,0 +1,4 @@
|
||||
# 1. The Dockerfile most tutorials start with: the whole JDK, the whole fat jar, one layer for both.
|
||||
FROM eclipse-temurin:25-jdk
|
||||
COPY target/app.jar /app/app.jar
|
||||
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
|
||||
@@ -0,0 +1,4 @@
|
||||
# 2. Same, on the JRE image. Smaller base, but every code change still re-ships the whole fat jar.
|
||||
FROM eclipse-temurin:25-jre
|
||||
COPY target/app.jar /app/app.jar
|
||||
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
|
||||
@@ -0,0 +1,26 @@
|
||||
# 6. A custom runtime: jdeps works out which JDK modules the application needs, jlink builds a
|
||||
# JRE containing only those, and it goes onto distroless java-base (glibc, no JRE of its own).
|
||||
# See docs/05-jlink.md for the module list this produced and the one it initially missed.
|
||||
FROM eclipse-temurin:25-jdk AS builder
|
||||
WORKDIR /builder
|
||||
COPY target/app.jar application.jar
|
||||
RUN java -Djarmode=tools -jar application.jar extract --layers --destination extracted
|
||||
# jdeps cannot see modules loaded reflectively. jdk.management is the one this application
|
||||
# needs: without it Micrometer silently drops 15 metrics, GC pauses and CPU usage among them.
|
||||
ARG EXTRA_MODULES="jdk.management"
|
||||
RUN MODULES="$(jdeps --ignore-missing-deps -q --recursive --multi-release 25 --print-module-deps \
|
||||
--class-path 'extracted/dependencies/lib/*' extracted/application/application.jar)" \
|
||||
&& MODULES="${MODULES}${EXTRA_MODULES:+,$EXTRA_MODULES}" \
|
||||
&& echo "jlink modules: $MODULES" | tee /builder/modules.txt \
|
||||
&& jlink --add-modules "$MODULES" --strip-debug --no-man-pages --no-header-files \
|
||||
--compress=zip-6 --output /builder/jre
|
||||
|
||||
FROM gcr.io/distroless/java-base-debian13:nonroot
|
||||
COPY --from=builder /builder/jre /opt/jre
|
||||
COPY --from=builder /builder/modules.txt /opt/jre/modules.txt
|
||||
WORKDIR /application
|
||||
COPY --from=builder /builder/extracted/dependencies/ ./
|
||||
COPY --from=builder /builder/extracted/spring-boot-loader/ ./
|
||||
COPY --from=builder /builder/extracted/snapshot-dependencies/ ./
|
||||
COPY --from=builder /builder/extracted/application/ ./
|
||||
ENTRYPOINT ["/opt/jre/bin/java", "-jar", "application.jar"]
|
||||
@@ -0,0 +1,13 @@
|
||||
# 4. Layered, on the Alpine (musl) JRE image.
|
||||
FROM eclipse-temurin:25-jre-alpine AS builder
|
||||
WORKDIR /builder
|
||||
COPY target/app.jar application.jar
|
||||
RUN java -Djarmode=tools -jar application.jar extract --layers --destination extracted
|
||||
|
||||
FROM eclipse-temurin:25-jre-alpine
|
||||
WORKDIR /application
|
||||
COPY --from=builder /builder/extracted/dependencies/ ./
|
||||
COPY --from=builder /builder/extracted/spring-boot-loader/ ./
|
||||
COPY --from=builder /builder/extracted/snapshot-dependencies/ ./
|
||||
COPY --from=builder /builder/extracted/application/ ./
|
||||
ENTRYPOINT ["java", "-jar", "application.jar"]
|
||||
@@ -0,0 +1,15 @@
|
||||
# 5. Layered, on distroless: no shell, no package manager, runs as uid 65532 (the :nonroot tag).
|
||||
# The extraction has to happen in a stage that HAS a shell - distroless cannot RUN anything.
|
||||
FROM eclipse-temurin:25-jre AS builder
|
||||
WORKDIR /builder
|
||||
COPY target/app.jar application.jar
|
||||
RUN java -Djarmode=tools -jar application.jar extract --layers --destination extracted
|
||||
|
||||
FROM gcr.io/distroless/java25-debian13:nonroot
|
||||
WORKDIR /application
|
||||
COPY --from=builder /builder/extracted/dependencies/ ./
|
||||
COPY --from=builder /builder/extracted/spring-boot-loader/ ./
|
||||
COPY --from=builder /builder/extracted/snapshot-dependencies/ ./
|
||||
COPY --from=builder /builder/extracted/application/ ./
|
||||
# Exec form is mandatory here: there is no /bin/sh to run a shell-form command.
|
||||
ENTRYPOINT ["java", "-jar", "application.jar"]
|
||||
@@ -0,0 +1,14 @@
|
||||
# 3. Spring Boot's layered extraction: dependencies, loader, snapshots and your code become four
|
||||
# separate layers, ordered from least to most likely to change.
|
||||
FROM eclipse-temurin:25-jre AS builder
|
||||
WORKDIR /builder
|
||||
COPY target/app.jar application.jar
|
||||
RUN java -Djarmode=tools -jar application.jar extract --layers --destination extracted
|
||||
|
||||
FROM eclipse-temurin:25-jre
|
||||
WORKDIR /application
|
||||
COPY --from=builder /builder/extracted/dependencies/ ./
|
||||
COPY --from=builder /builder/extracted/spring-boot-loader/ ./
|
||||
COPY --from=builder /builder/extracted/snapshot-dependencies/ ./
|
||||
COPY --from=builder /builder/extracted/application/ ./
|
||||
ENTRYPOINT ["java", "-jar", "application.jar"]
|
||||
@@ -0,0 +1,5 @@
|
||||
# 8. A trap, not a recommendation: shell-form ENTRYPOINT. Whether SIGTERM reaches the JVM depends
|
||||
# on what /bin/sh does with "-c" - measured, not assumed, in docs/07-pid1-and-signals.md.
|
||||
FROM eclipse-temurin:25-jre
|
||||
COPY target/app.jar /app/app.jar
|
||||
ENTRYPOINT java -jar /app/app.jar
|
||||
@@ -0,0 +1,4 @@
|
||||
# 8b. The same shell-form ENTRYPOINT where /bin/sh is BusyBox ash instead of dash.
|
||||
FROM eclipse-temurin:25-jre-alpine
|
||||
COPY target/app.jar /app/app.jar
|
||||
ENTRYPOINT java -jar /app/app.jar
|
||||
@@ -0,0 +1,4 @@
|
||||
# 8c. What people actually write: a shell form with a second command in front of java.
|
||||
FROM eclipse-temurin:25-jre
|
||||
COPY target/app.jar /app/app.jar
|
||||
ENTRYPOINT echo "starting revision $(date +%s)" && java -jar /app/app.jar
|
||||
Reference in New Issue
Block a user