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
4.3 KiB
4. Buildpacks: what the builder decides for you
← 3. Layered jars · Index · Next: 5. jlink →
mvn spring-boot:build-image with no configuration used paketobuildpacks/builder-noble-java-tiny
- the Spring Boot 4.1.1 default - and six of its 26 buildpacks took part:
ca-certificates,bellsoft-liberica,syft,executable-jar,dist-zip,spring-boot.
What you get without writing a line of Dockerfile, from the build log and
image-matrix.txt:
- Java version from the jar.
$BP_JVM_VERSIONshows21as its default, thenUsing Java version 25 extracted from MANIFEST.MF- the buildpack readsBuild-Jdk-Spec. - A non-root user,
1002:1001, and no shell (the tiny run image). - A reproducible image. Creation date is fixed (Docker shows "46 years ago" - 1980), so the same input gives the same digest.
- A memory calculator that runs before the JVM and sets
-Xmx, metaspace, code cache and thread stacks from the container's memory limit - see below, it can refuse to start the app. - An SBOM layer from Syft.
- Spring Cloud Bindings on the classpath, which reads Kubernetes service bindings into Spring
properties.
BP_SPRING_CLOUD_BINDINGS_DISABLED=trueremoves it.
The size
345 MB on disk, and most of it is one layer:
276MB Layer: 'jre', Created by buildpack: paketo-buildpacks/[email protected]
Temurin's 25 JRE is 200 MB. BellSoft's JRE carries a second VM: lib/client is 74 MB (its own
libjvm.so plus two CDS archives) next to the 85 MB lib/server. BP_JVM_JLINK_ENABLED=true asks
the buildpack to jlink a smaller runtime - with the caveat in chapter 5.
At 512 MiB the image does not start
## docker run -m 512m
unable to calculate memory configuration
fixed memory regions require 595872K which is greater than 512M available for allocation: -XX:MaxDirectMemorySize=10M, -XX:MaxMetaspaceSize=83872K, -XX:ReservedCodeCacheSize=240M, -Xss1M * 250 threads
ERROR: failed to launch: exec.d: failed to execute exec.d file at path '/layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/memory-calculator': exit status 1
state: exited (exit 82)
The calculator reserves 1 MiB of stack for each of 250 threads plus a 240 MiB code cache and
metaspace sized from the class count, before any heap. 512 MiB - a very common Kubernetes
limits.memory - is not enough, and the container exits with code 82 before Java runs. The same jar
in any of the Dockerfile-built images starts fine at 512 MiB.
Either give it more (-m 768m → -Xmx190559K, a quarter of the limit) or tell it the truth about
threads: BPL_JVM_THREAD_COUNT=50 → starts at 512 MiB with -Xmx133215K. With virtual threads,
250 platform threads is a generous assumption.
Behind a TLS-intercepting proxy
The Java buildpack downloads the JRE at build time, and the Spring Boot buildpack downloads Spring
Cloud Bindings from Maven Central. Inside a corporate proxy both fail. Two profiles in the
pom.xml handle it:
corporate-proxy- build container on the host network,HTTPS_PROXYpassed through, and aca-certificatesbinding so the build trusts the proxy's CA (Added 2 additional CA certificate(s) to system truststorein the log). WithBP_EMBED_CERTS=false- the default - the CA is not baked into the runtime image.no-maven-central- adependency-mappingbinding that points the buildpack at a local copy of the jar, keyed by the sha256 in the buildpack'sbuildpack.toml.
Both bindings are merged with combine.children="append", so the profiles compose:
-Pcorporate-proxy,no-maven-central.
Two traps met on the way
- The run image is pinned by tag inside the builder. The builder pulled as
:latestasked forpaketobuildpacks/ubuntu-noble-run-tiny:0.0.130; withpullPolicy=IF_NOT_PRESENTand only:latestpresent locally, the build still tried Docker Hub. Tagging the same digest as0.0.130fixed it. -Dspring-boot.build-image.imageNameis ignored if the pom sets<image><name>- explicit configuration beats the user property, so the "second" build silently overwrote the first image. The pom uses a${buildpacks.image}property instead.