Short answer: the Digest: line from docker buildx imagetools inspect is the value to use — replace the tag with @<that digest>, keeping the original tag as a trailing comment so a human reading the Dockerfile still knows which version it is.
Most Dockerfiles start like this:
FROM node:18
18 is a tag, and a tag is just a name someone attached to an image build — the publisher can push a new build to that same tag at any time. Pinning to a content digest instead means the reference cannot be silently changed: a digest addresses one exact set of bytes.
Use docker buildx imagetools inspect against the image and tag. This queries the registry's manifest directly — the same request a docker pull would make to decide what to fetch — without pulling the full image:
docker buildx imagetools inspect node:18
That prints output including a line like:
Name: docker.io/library/node:18
MediaType: application/vnd.oci.image.index.v1+json
Digest: sha256:7e2895622100fa27781b8d926d4c7d719f26061ba1e78facbf3f6352a6407965
The Digest: line is the value to use. (No buildx available? docker manifest inspect --verbose node:18 or the third-party crane digest node:18 answer the same question.)
Keep the original tag as a trailing comment, so a human reading the Dockerfile still knows which version it is without decoding the hash:
FROM node@sha256:7e2895622100fa27781b8d926d4c7d719f26061ba1e78facbf3f6352a6407965 # 18
That line now resolves to exactly one set of bytes, forever. Upgrading later is a deliberate, reviewable diff: look up the digest for a newer tag and replace both the hash and the comment.
A multi-stage build can have several FROM lines, and a single unpinned reference is enough to be the weak link — including the final stage that actually ships. Paste your Dockerfile into Docker Base Image Pin Check to find every mutable reference at once, each with its own ready-to-run imagetools inspect command, or run the CLI in your own repository:
python3 check.py --fail-on-risk
That exits with a nonzero status if any base image is still referenced by a tag instead of a digest — wire it into your own CI to block a newly-added unpinned base image before it merges.
node:18 is a moving target for the entire support lifetime of Node 18: the maintainers push new patch and security builds to that same tag routinely, which is the point of the tag — you want those updates when you rebuild deliberately. The risk this addresses is the same update landing silently, on someone else's docker build, with nothing in your own commit history to say the base image changed. A digest reference removes that ambiguity: the same digest always means the same bytes, so a change of base image is a change you make on purpose, in a diff you can review.