I’ll often see Dockerfiles pulling in base images with a FROM instruction which look like this:

FROM <image-name>:<tag> ...

What many developers don’t realise (and I was guilty of this too) is that the tag is mutable.

By default, Docker pulls from Docker Hub. It’s an image registry similar to package registries you might be more familiar with like npm or pypi.

Unless the Dockerhub author enforces immutability, nothing prevents prevents an image from being republished under the same tag. By default, tags are mutable.

Docker does not have a lockfile system in place. However, docker, docker-compose and Kubernetes have some defenses in-place against this - by default they will not pull an updated image for a tag if a cached version already exists locally. Kubernetes users can make use of imagePullPolicy: IfNotPresent. This behaviour can reduce exposure but should not be relied upon as a security control.

But this does not protect against users pulling without an existing local cache. I would also imagine a lot of automated CI/CD setups out there are not using caching properly.


The best case scenario is: somehow some broken code gets pushed to that tag and your build pipeline or app breaks.

Worst case scenario: malicious binaries get pushed to that tag, and now you’re pwned.

Supply chain attacks are increasingly becoming a major threat to teams globally. See for example the recent Sha1-hulud attack.


The goal is deterministic builds: the same input always produces the same output.

So, instead of doing:

COPY --from=mwader/static-ffmpeg:7.1 /ffmpeg /usr/local/bin/ffmpeg

Pin untrusted images to their SHA-256 digest:

COPY --from=mwader/static-ffmpeg:7.1@sha256:a8090df5f5608daef387e1b2e93b98aaacb4d92153ad904e7d715c725724fca4 /ffmpeg /usr/local/bin/ffmpeg

This guarantees that the image you are pulling from is the image that you think it is.

To obtain a digest for local images:

docker images --digests

There are some trade-offs here.

You need to be certain that the digest you are pinning to is what you think it is. Nobody has time to manually inspect images all day. I think taking an approach similar to what pnpm does for npm packages - waiting x days/weeks before trusting it is good enough for most people.

Pinning like this might also hinder your automatic patching, and may require some additional maintenance. IMO, that is better than the alternative.

Tools like Renovate and Dependabot can automate this - they’ll detect pinned digests in your Dockerfiles and open PRs when new versions are published, so you get both immutability and timely updates without manual upkeep.

For internal base images, digest pinning is less critical since you control the tag semantics.