An inventory maintained by hand is out of date the day it is written; one generated by the build is correct by construction.
# per build, alongside the image
composer show --format=json > sbom/composer.json
npm ls --all --json > sbom/npm.json
# the base image's packages, which the other two miss
docker run --rm --entrypoint sh app:"$TAG" -c
'apk info -v 2>/dev/null || dpkg -l' > sbom/os.txt
# tagged with the image digest, not the tag — tags move
echo "$(docker image inspect app:"$TAG" -f '{{index .RepoDigests 0}}')"
> sbom/digest.txt
Three sources are needed because no single tool sees all of them: the application dependencies, the JavaScript ones and the operating system packages in the base image, which is where a surprising share of advisories land. Keying the record on the image digest rather than the tag is what makes it answerable later — a tag is a moving pointer and the question is always about a specific deployed artefact.