docker history lists an image's build steps from the newest down, with the instruction that made each one and the size it added. It is the quickest way to find out why someone else's image is large:
docker history l3-layers:1.0 \
--format 'table {{.ID}}\t{{.CreatedSince}}\t{{.Size}}\t{{.CreatedBy}}'IMAGE CREATED SIZE CREATED BY effc91e93bde 5 minutes ago 0B CMD ["cat" "/hello.txt"] <missing> 5 minutes ago 8.19kB COPY hello.txt /hello.txt # buildkit <missing> 5 minutes ago 4.1kB RUN /bin/sh -c rm /big.bin # buildkit <missing> 5 minutes ago 21MB RUN /bin/sh -c dd if=/dev/urandom of=/big.bi… <missing> 7 days ago 0B CMD ["/bin/sh"] <missing> 7 days ago 9.08MB ADD alpine-minirootfs-3.24.2-x86_64.tar.gz /…
The bottom two rows come from alpine:3's own build, which added its root filesystem a week earlier. <missing> does not signal a problem: BuildKit 10,294 does not create a separate image for each step, so only the final row has an ID. The history travels with the image config, so --no-trunc also reveals any secret passed on a command line or in ARG (Secrets in Images).
The manifest is the registry's view: which config and layer blobs make up the image for one platform. For a multi-platform tag, the index lists one manifest per architecture, which is how docker pull chooses the right one:
docker buildx imagetools inspect node:24-slim | grep Platform | head -4Platform: linux/amd64 Platform: unknown/unknown Platform: linux/arm64/v8 Platform: unknown/unknown
Each unknown/unknown entry is an attestation (provenance or SBOM data) attached to the manifest before it, not an image; Container Security reads them. The free tools dive 54,614 (https://github.com/wagoodman/dive 54,614 ) and crane 4,061 (https://github.com/google/go-containerregistry 4,061 ) browse layers and manifests interactively and from scripts.