Pin Docker Base Images by Digest and Keep Them Updated
By OpsAtlas · · 3 min read
Table of contents
FROM node:22-slim looks like a fixed version, but it is a pointer. The image it refers to changes every time Node.js ships a 22.x release or Debian publishes a security fix. Rebuild the same commit next month and you may get a different base image, sometimes with a behaviour change that breaks the build or the app.
That is fine until you need to reproduce an old build, roll back exactly, or explain why an image that worked yesterday doesn’t today. The answer is to pin the base image to its digest and let a bot move the pin forward.
Tags move, digests don’t
FROM line | What you get | Changes when |
|---|---|---|
node or node:latest | The newest release | Every release, including major versions |
node:22-slim | The newest 22.x on Debian slim | Every 22.x release and base OS update |
node:22.11.0-slim | A specific Node.js version | The image can still be rebuilt, for example for Debian security fixes |
node:22-slim@sha256:… | Exactly one image | Never |
A tag is a name that the publisher can point at a new image at any time. Official images are rebuilt regularly under the same tags, so even an exact version tag is not a guarantee. A digest is the SHA-256 of the image manifest, so it always means the same bytes.
Find the digest
docker buildx imagetools inspect node:22-slim
The Digest: line at the top is the digest of the multi-platform index. Pin that one, not the digest of a single platform, so the same FROM line still works for both amd64 and arm64 builds.
FROM node:22-slim@sha256:<digest-from-the-command-above>
When a digest is present, Docker ignores the tag and pulls by digest. Keep the tag anyway: it tells people which version the digest is, and it tells update bots which tag to follow.
Let a bot move the pin
A pinned image never gets security fixes on its own. Pinning without automated updates trades surprise breakage for slowly rotting images. Both popular bots can update digests:
Renovate. Add the docker:pinDigests preset to renovate.json. Renovate pins existing FROM lines to digests in a first PR, then opens PRs whenever the tag points at a new image:
{
"extends": ["config:recommended", "docker:pinDigests"]
}
If you group updates, put digest bumps in the same weekly PR as other non-major updates. You get one review a week instead of a PR every time Debian ships a fix.
Dependabot. Add the docker ecosystem to .github/dependabot.yml. Dependabot updates the tag and the digest of each FROM line it finds in the directory:
version: 2
updates:
- package-ecosystem: docker
directory: /
schedule:
interval: weekly
Each update is an ordinary pull request, so your CI builds the new base image and runs the tests before anything is merged. That is the review step a moving tag skips.
Pin every stage
In a multi-stage build, the build stages matter too. An unpinned golang:1.23 builder can change the compiler between two builds of the same commit. Pin every FROM, not just the last one.
Avoid putting the version in an ARG (FROM node:${NODE_VERSION}-slim) unless you also tell your update bot about it. Bots read literal FROM lines; a version hidden in a variable usually never gets updated.
The same rule applies in Kubernetes
A Deployment that references image: myapp:latest has the same problem one level up. Nodes that pull at different times can end up running different images under the same name. Release with a version tag, and pin to a digest when you need guarantees. Your CI can write the digest of the image it just pushed into the manifest.
Check your files
The Dockerfile linter flags every FROM that uses :latest or no tag at all, in every stage, and suggests the pinned form. A versioned tag such as node:22-slim passes, since pinning by digest only makes sense with an update bot. The Kubernetes YAML validator does the same for container images in your manifests.
Pinning is one of several habits that make images predictable. For the others, see Dockerfile mistakes that leak secrets and bloat images and multi-stage Docker builds.
Try it yourself
Dockerfile linter and fixer
Lint a Dockerfile in your browser: find root users, unpinned images, leaked secrets and bloated layers, then apply safe fixes in one click.
DockerBetaKubernetes YAML validator and visualizer
Paste Kubernetes manifests to get a resource diagram and a security and best-practice review: privileged pods, root, probes, limits, deprecated APIs.
KubernetesBeta