# 2. Measuring size [← 1. The variants](01-the-variants.md) · [Index](../README.md) · Next: [3. Layered jars →](03-layered-jars.md) "Image size" means at least three different numbers: | Number | What it is | How it is measured here | |---|---|---| | on disk | uncompressed layers, what a node stores | `docker image inspect -f '{{.Size}}'` on the classic `overlay2` store | | pushed | compressed layer blobs + config, what a registry stores and a cold node downloads | sum of `layers[].size` + `config.size` from `crane manifest` | | rebuild delta | the blobs a push actually uploads after a change | [chapter 3](03-layered-jars.md) | [`measure.sh`](../scripts/measure.sh) pushes every variant to a throwaway registry (`crane registry serve --address localhost:5000` - in-memory, no Docker Hub account needed) and reads the manifests back. ## `docker images` disagrees with itself Docker Engine 29 can use two image stores. The same eleven tarballs, loaded into each ([`image-store-size-difference.txt`](output/image-store-size-difference.txt)): | Image | containerd store | classic overlay2 | |---|---|---| | `eclipse-temurin:25-jre` | 496 MB | 352 MB | | `gcr.io/distroless/java25-debian13:nonroot` | 305 MB | 226 MB | | `paketobuildpacks/builder-noble-java-tiny` | 1.26 GB | 868 MB | The containerd store's figure is larger across the board. Size comparisons from blog posts - or between two colleagues' laptops - are only comparable if they used the same store, and the containerd store is the default for new Docker installations. This project measures on the classic store because its number is the unpacked size alone. ## The result [`image-matrix.txt`](output/image-matrix.txt): ``` variant on disk pushed layers user shell packages ready ms Started RSS JVM fatjar-jdk 456M 178M 7 root yes 115 (dpkg) 5362 3.932s 169.5MiB Eclipse 25.0.4 fatjar-jre 377M 144M 7 root yes 106 (dpkg) 5091 3.944s 182.1MiB Eclipse 25.0.4 layered-jre 377M 144M 11 root yes 106 (dpkg) 4272 3.356s 169.5MiB Eclipse 25.0.4 layered-alpine 251M 98M 10 root yes 44 (apk) 4634 3.775s 145.9MiB Eclipse 25.0.4 layered-distroless 252M 97M 39 65532 no 25 (status.d) 4118 3.254s 164.3MiB Eclipse 25.0.4.1 jlink-distroless 119M 76M 39 65532 no 24 (status.d) 4386 3.39s 165.7MiB Eclipse 25.0.4 aot-cache 440M 160M 12 root yes 106 (dpkg) 2412 1.638s 174.6MiB Eclipse 25.0.4 buildpacks 345M 125M 20 1002:1001 no 10 (status.d) 4726 3.615s 183.6MiB BellSoft 25.0.4 jib 377M 144M 10 root yes 106 (dpkg) 4323 3.457s 162.5MiB Eclipse 25.0.4 ``` - *ready ms* is `docker run` to the first 200 from `/actuator/health/readiness`; *Started* is Spring Boot's own figure. Median of three on a 2-vCPU VM - treat differences under ~0.3 s as noise. - *packages* counts OS packages from the image filesystem without running anything in it (dpkg `status`, distroless/Paketo `status.d/`, or `apk`), which works for images with no shell. - Every Temurin-based variant runs as **root**: the official images set no `USER`. A note on method: the first run of this table had distroless starting two seconds slower than everything else. A k3s image import was running on the same two CPUs at the time. Nothing else runs during `measure.sh` now.