docker history lists each layer with its size and the instruction that made it, so it shows what a stage added:
F='{{.Size}}\t{{.CreatedBy}}'
docker history l3-booknest-api:1.0 --format "$F" | sed -n 6p | cut -c1-60
docker history l3-booknest-api:1.1 --format "$F" | sed -n 8p | cut -c1-608.26MB RUN |1 NODE_ENV=production /bin/sh -c npm ci… 5.51MB COPY /app/node_modules ./node_modules # buil…
Of the single-stage npm 2,036 ci layer's 8.26 MB, only 5.51 MB is node_modules; the rest is npm's leftovers. A runtime stage on gcr.io/distroless/nodejs24-debian13 (its healthcheck calling /nodejs/bin/node, which is outside PATH there) shows how much the base image still decides:
| Image | Disk usage | Download | Shell and npm |
|---|---|---|---|
| Single stage on node:24 | 1.64 GB | 411 MB | Yes, plus GCC |
| Single stage on node:24-slim (BookNest Dockerfile) | 339 MB | 82.1 MB | Yes |
| Multi-stage, node:24-slim runtime | 336 MB | 81.6 MB | Yes |
| Multi-stage, distroless runtime | 221 MB | 56.1 MB | No |
The distroless variant turned healthy and served the API, but docker exec into it fails with exec: "sh": executable file not found, so BookNest keeps node:24-slim and its shell (docker debug debugs distroless). That base is 332 MB of the 336: the real gains here are the test target and a runtime without extras.