$ -weight: 500;">docker save alpine:3.20 -o alpine.tar
$ layer-blame alpine.tar Image total: 8.4 MB across 1 layers · package attribution: 100% Layer 0 8.4 MB ADD alpine-minirootfs-3.20.10-aarch64.tar.gz / 4.8 MB libcrypto3 pkg ← largest line highlighted 911.7 KB libssl3 pkg 906.0 KB busybox pkg 706.5 KB musl pkg 327.1 KB -weight: 500;">apk-tools pkg
$ -weight: 500;">docker save alpine:3.20 -o alpine.tar
$ layer-blame alpine.tar Image total: 8.4 MB across 1 layers · package attribution: 100% Layer 0 8.4 MB ADD alpine-minirootfs-3.20.10-aarch64.tar.gz / 4.8 MB libcrypto3 pkg ← largest line highlighted 911.7 KB libssl3 pkg 906.0 KB busybox pkg 706.5 KB musl pkg 327.1 KB -weight: 500;">apk-tools pkg
$ -weight: 500;">docker save alpine:3.20 -o alpine.tar
$ layer-blame alpine.tar Image total: 8.4 MB across 1 layers · package attribution: 100% Layer 0 8.4 MB ADD alpine-minirootfs-3.20.10-aarch64.tar.gz / 4.8 MB libcrypto3 pkg ← largest line highlighted 911.7 KB libssl3 pkg 906.0 KB busybox pkg 706.5 KB musl pkg 327.1 KB -weight: 500;">apk-tools pkg
$ -weight: 500;">docker save python:3.12-slim -o py.tar
$ layer-blame --top 5 py.tar Image total: 137.7 MB across 4 layers · package attribution: 69% Layer 0 95.8 MB # debian.sh --arch 'arm64' out/ 'trixie' ... 22.5 MB libc6 pkg 9.1 MB coreutils pkg 7.4 MB perl-base pkg 7.3 MB libssl3t64 pkg 6.6 MB util-linux pkg Layer 2 38.2 MB RUN /bin/sh -c set -eux; savedAptMark="$(-weight: 500;">apt-mark showmanual)"; … 6.3 MB /usr/local/lib/libpython3.12.so.1.0 file 1.8 MB /usr/local/lib/python3.12/ensurepip/_bundled/-weight: 500;">pip-...whl file 1.1 MB /usr/local/lib/python3.12/lib-dynload/unicodedata...so file ...
$ -weight: 500;">docker save python:3.12-slim -o py.tar
$ layer-blame --top 5 py.tar Image total: 137.7 MB across 4 layers · package attribution: 69% Layer 0 95.8 MB # debian.sh --arch 'arm64' out/ 'trixie' ... 22.5 MB libc6 pkg 9.1 MB coreutils pkg 7.4 MB perl-base pkg 7.3 MB libssl3t64 pkg 6.6 MB util-linux pkg Layer 2 38.2 MB RUN /bin/sh -c set -eux; savedAptMark="$(-weight: 500;">apt-mark showmanual)"; … 6.3 MB /usr/local/lib/libpython3.12.so.1.0 file 1.8 MB /usr/local/lib/python3.12/ensurepip/_bundled/-weight: 500;">pip-...whl file 1.1 MB /usr/local/lib/python3.12/lib-dynload/unicodedata...so file ...
$ -weight: 500;">docker save python:3.12-slim -o py.tar
$ layer-blame --top 5 py.tar Image total: 137.7 MB across 4 layers · package attribution: 69% Layer 0 95.8 MB # debian.sh --arch 'arm64' out/ 'trixie' ... 22.5 MB libc6 pkg 9.1 MB coreutils pkg 7.4 MB perl-base pkg 7.3 MB libssl3t64 pkg 6.6 MB util-linux pkg Layer 2 38.2 MB RUN /bin/sh -c set -eux; savedAptMark="$(-weight: 500;">apt-mark showmanual)"; … 6.3 MB /usr/local/lib/libpython3.12.so.1.0 file 1.8 MB /usr/local/lib/python3.12/ensurepip/_bundled/-weight: 500;">pip-...whl file 1.1 MB /usr/local/lib/python3.12/lib-dynload/unicodedata...so file ...
-weight: 500;">docker save <image> -o image.tar
layer-blame [flags] image.tar
-weight: 500;">docker save <image> -o image.tar
layer-blame [flags] image.tar
-weight: 500;">docker save <image> -o image.tar
layer-blame [flags] image.tar
# Prebuilt binary (Linux/macOS/Windows, amd64+arm64) — from the latest release
-weight: 500;">curl -sSfL https://github.com/mk668a/layer-blame/releases/latest/download/layer-blame_<version>_linux_amd64.tar.gz \ | tar -xz layer-blame # or with Go
go -weight: 500;">install github.com/mk668a/layer-blame@latest
# Prebuilt binary (Linux/macOS/Windows, amd64+arm64) — from the latest release
-weight: 500;">curl -sSfL https://github.com/mk668a/layer-blame/releases/latest/download/layer-blame_<version>_linux_amd64.tar.gz \ | tar -xz layer-blame # or with Go
go -weight: 500;">install github.com/mk668a/layer-blame@latest
# Prebuilt binary (Linux/macOS/Windows, amd64+arm64) — from the latest release
-weight: 500;">curl -sSfL https://github.com/mk668a/layer-blame/releases/latest/download/layer-blame_<version>_linux_amd64.tar.gz \ | tar -xz layer-blame # or with Go
go -weight: 500;">install github.com/mk668a/layer-blame@latest - Load the tarball / OCI layout via go-containerregistry, which normalizes Docker-save, BuildKit, and containerd/OCI formats for you.
- Walk each layer's filesystem diff, recording every added file and its size. Whiteout (deletion) markers are skipped — you can't blame a layer for bytes it removed.
- Build a file→package index from the image's own package DBs: /lib/-weight: 500;">apk/db/installed (Alpine) and /var/lib/dpkg/info/*.list (Debian/Ubuntu).
- For each layer, group added bytes by owning package. Files with no owner are reported individually, so a large unattributed artifact still surfaces by name.
- Map each layer back to the Dockerfile instruction that created it (created_by from the image config) and print the table. - scratch / distroless have no package database, so attribution falls back to file level. Still useful, no package names.
- Multi-stage COPY --from files are disconnected from their origin package, so they show as unattributed in the destination layer.
- v1 handles -weight: 500;">apk (Alpine) and dpkg (Debian/Ubuntu) only. rpm and language package managers (-weight: 500;">npm, -weight: 500;">pip) aren't in yet.