skip to content

What infrastructure and architecture constraints must you plan for when running bootBuildImage native builds in CI?

level: seniorimportance: should knowfreq 30%

answer

  1. Needs container daemon / DOCKER_HOST
  2. native-image = RAM + CPU hog, minutes not seconds
  3. No cross-compile — build on target arch or emulate
  4. Multi-arch = matrix + manifest list
  5. Dedicated sized runners, cache builder layers

basics

~20 s

You need a container daemon (or DOCKER_HOST), plenty of memory and CPU because native-image is heavy, and a builder whose architecture matches your target — buildpacks don't cross-compile, so build arm64 images on an arm64 runner.

solid answer

~40 s

bootBuildImage runs the whole native compilation **inside a container**, so CI needs an accessible container runtime — a local Docker daemon, a rootless engine, or a remote one via `DOCKER_HOST`. GraalVM `native-image` is very **CPU- and memory-intensive**; underprovisioned runners OOM or time out, so allocate several GB and enough cores. Crucially, the buildpack **does not cross-compile**: the produced binary targets the **builder/host architecture**, so to ship an `arm64` image you must run the build on an arm64 host (or use emulation, which is much slower). Builds are slow, so cache the builder and buildpack layers and expect long durations. Also handle registry auth for both pulling the (possibly private) builder and pushing the result. These constraints often push teams to dedicated, sized native-build runners.

go deeper

for a junior

Know a container daemon is needed and native builds are slow.

for a middle

Add the memory/CPU footprint and DOCKER_HOST remote-daemon option.

for a senior

Explain no-cross-compile, per-arch matrix builds, manifest lists, and caching.

for a principal

Design CI topology: dedicated runners, build cadence, arch strategy, and GraalVM-version governance via builder tags.

## Container runtime requirement Unlike a jar build, `bootBuildImage` executes the buildpack lifecycle **inside a container**. CI therefore needs a container engine reachable by the plugin: - a Docker daemon on the runner, - a Docker-API-compatible engine (Podman with the Docker socket, etc.), or - a **remote daemon via the `DOCKER_HOST` env var** (plus TLS vars if secured). Docker-in-Docker or a mounted socket is common on hosted CI. No daemon = the build cannot start. ## Compute footprint GraalVM `native-image` performs whole-program **closed-world static analysis** and AOT compilation — this is one of the heaviest steps in the JVM ecosystem. - It commonly needs **several gigabytes of RAM** and benefits from multiple cores; the container/daemon must be allowed that memory or the build fails mid-compile (often surfacing as an OOM/kill). - Wall-clock times of many minutes are normal, far longer than a jar build. Budget CI minutes and timeouts accordingly. ## No cross-compilation / architecture pinning The buildpack compiles for the **architecture of the builder image as it runs on the host**. There is **no cross-compilation**: on an x86_64 host you get an amd64 binary; to produce an **arm64** image you run the build on an **arm64 host** (e.g. an ARM CI runner) or fall back to **QEMU emulation**, which works but is dramatically slower. Multi-arch images therefore require a build per architecture (e.g. a matrix across amd64 and arm64 runners) and a manifest list to combine them — the single `bootBuildImage` invocation won't do it alone. ## Caching and registry auth **Caching.** To keep repeat builds bearable, persist the buildpack/builder layers and the native-image caches between runs where your CI allows (volume caches, keeping the daemon warm). Cold runs re-pull the builder and redo analysis. **Registry auth.** Configure credentials for: - the **builderRegistry** (if the builder image is private/mirrored), and - the **publishRegistry** (to push results with `publish=true`). Store these as CI secrets; never bake them into the build file. ## Consequences / recommendations Because of the memory/time/arch constraints, teams frequently: - run native builds on **dedicated, generously-sized runners** rather than shared standard ones, - run them on a schedule or on release branches rather than every commit, - and keep the fast JVM jar build for routine PR CI. Match the runner architecture to the deployment target to avoid emulation. ## Gotchas - (1) A too-small Docker memory limit is the classic silent failure. - (2) Building amd64 on Apple-silicon (or vice versa) via emulation can 3-5x the time. - (3) Remote `DOCKER_HOST` builds must transfer the build context/image over the wire. - (4) The builder tag pins the GraalVM version — upgrading GraalVM is a builder bump, coordinated across runners.

  • You need both amd64 and arm64 native images. How do you produce them with bootBuildImage?
    Run the build on each architecture separately — an amd64 runner and an arm64 runner (or QEMU emulation) — since the buildpack can't cross-compile, then combine the two single-arch images into a multi-arch manifest list. One bootBuildImage call yields only the host architecture.
  • A native build passes locally but is killed in CI. First thing you check?
    Memory allocated to the container/daemon. native-image needs several GB; an undersized CI runner or Docker memory limit OOM-kills the compile. Bump memory/CPU or move to a dedicated native-build runner.

saying these in an interview costs you the question

  • Assuming buildpacks cross-compile to any target arch
  • Expecting native-image build times comparable to a jar build
  • Forgetting a container daemon is required for bootBuildImage

context