# 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/bellsoft-liberica@11.8.3 ``` 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 ``** - explicit configuration beats the user property, so the "second" build silently overwrote the first image. The pom uses a `${buildpacks.image}` property instead.