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,80 @@
|
||||
# 4. Buildpacks: what the builder decides for you
|
||||
|
||||
[← 3. Layered jars](03-layered-jars.md) · [Index](../README.md) · Next: [5. jlink →](05-jlink.md)
|
||||
|
||||
`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`](output/image-matrix.txt):
|
||||
|
||||
- **Java version from the jar.** `$BP_JVM_VERSION` shows `21` as its default, then
|
||||
`Using Java version 25 extracted from MANIFEST.MF` - the buildpack reads `Build-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=true` removes 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](05-jlink.md).
|
||||
|
||||
## At 512 MiB the image does not start
|
||||
|
||||
[`buildpacks-memory.txt`](output/buildpacks-memory.txt):
|
||||
|
||||
```
|
||||
## 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`](../pom.xml) handle it:
|
||||
|
||||
- `corporate-proxy` - build container on the host network, `HTTPS_PROXY` passed through, and a
|
||||
`ca-certificates` [binding](../bindings/ca-certificates) so the build trusts the proxy's CA
|
||||
(`Added 2 additional CA certificate(s) to system truststore` in the log). With
|
||||
`BP_EMBED_CERTS=false` - the default - the CA is not baked into the runtime image.
|
||||
- `no-maven-central` - a `dependency-mapping` [binding](../bindings/dependency-mapping) that points
|
||||
the buildpack at a local copy of the jar, keyed by the sha256 in the buildpack's `buildpack.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 `:latest` asked for
|
||||
`paketobuildpacks/ubuntu-noble-run-tiny:0.0.130`; with `pullPolicy=IF_NOT_PRESENT` and only
|
||||
`:latest` present locally, the build still tried Docker Hub. Tagging the same digest as `0.0.130`
|
||||
fixed it.
|
||||
- **`-Dspring-boot.build-image.imageName` is 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.
|
||||
Reference in New Issue
Block a user