Files
spring-boot-demo/docker-images/docs/04-buildpacks.md
T
asmhatreandClaude Opus 5 644da9e65e 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
2026-09-11 17:11:46 +00:00

81 lines
4.3 KiB
Markdown

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