How do you keep your Dockerfile FROM sha tags up to date?

For my trusty followers who have been crying out for a technology-based post – TODAY IS THE DAY.

Due to recent supply chain attacks; more platforms are going down the route of suggesting sha-pinning dependencies

GitHub has a flag that ensures that all your GitHub Action dependencies are explicitly tied to a sha, instead of just a version tag (e.g. @v1)

This helps to put you in control of version upgrades, as each time the author releases a new version.

In the npm world, your package.json file defines the ‘range’ of dependencies that you ‘directly’ depend on, and the package-lock.json is a reference to all of the reified dependency tree versions that were pulled when you ‘built’ the package.

This produced repeatable builds (when using `npm ci`), as it only ever downloaded the dependencies ‘pinned’ by the lockfile. As npm follows SemanticVersioning, when new packages are updated, the dependency tree is re-worked out, and dependabot can make suggestions for how to update your package-lock.json file to satisfy both generic updates, and security patches.

It’s great for npm (SemVer), it’s great for GHA (SemVer), but it’s also capable of supporting Dockerfiles.

FROM node:20-alpine

An innocuous Dockerfile, build using the Official Node image, and follows Docker’s own documentation (from 2022).

Way back in 2020, the first hit (for me) on Google is Bálint Biró‘s helpful blogpost on how to follow this approach for Docker images within the context of tools like docker-compose; and the official Dockerfile doc shows how to do this using the FROM argument in a dockerfile:

FROM node:20-alpine@sha256:3bc9a4c4cc25cfde1e8f946341c85f333c36517aafda829b4bb7e785e9b5995c 

Neat.

However, the management of these images in DockerHub is kinda a different paradigm. The office ‘node’ image, of which 20-alpine is just a label, has quite a load of other labels:

https://hub.docker.com/_/node

There are tens of labels, and the label I’ve targeted is mutable. It automatically keeps me up to date whenever the official maintainers release a new version. However, it’s the same anti-pattern as the one that Github Actions recently fixed with their sha pinning.

So, if I want to maintain 20-alpine, and I want to pin to a sha, so that I only get an update if the maintainer posts a new version of the 20-alpine label to docker hub, that’s possible right?

Well, I tested it and put a Dockerfile the following:

FROM node:20-alpine@sha256:3bc9a4c4cc25cfde1e8f946341c85f333c36517aafda829b4bb7e785e9b5995c

and a dependabot.yml like so:

# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates

version: 2
updates:
  - package-ecosystem: "npm" # See documentation for possible values
    directory: "/" # Location of package manifests
    schedule:
      interval: "weekly"
  - package-ecosystem: "github-actions" # See documentation for possible values
    directory: "/" # Location of package manifests
    schedule:
      interval: "weekly"
  - package-ecosystem: "docker"
    directory: "/" # Location of package manifests
    schedule:
      interval: "daily"

and to my (un-surprise, as I’d read the docs), it didn’t quite work as anticipated, with the next PR suggesting the following change:

Bump node from 20-alpine to 25-alpine !

Now, node’s own guidance is as follows:

Major Node.js versions enter Current release status for six months, which gives library authors time to add support for them. After six months, odd-numbered releases (9, 11, etc.) become unsupported, and even-numbered releases (10, 12, etc.) move to Active LTS status and are ready for general use. LTS release status is “long-term support”, which typically guarantees that critical bugs will be fixed for a total of 30 months. Production applications should only use Active LTS or Maintenance LTS releases.

So this PR is taking me from a current LTS release, in maintenace til April ’26, to a current version that’s never going to enter LTS and not recommended for production use.

I think that it’s behaving like this because the implementation pattern that the official node maintains have taken to have loads of labels against a single image. Dependabot for docker isn’t particularly smart, and looks for a temporally later tag against the node image, rather than using the 20-alpine label to contextualise the sha and lock it against the label+sha as defined in my lovely Dockerfile.

Now, no grief to the maintainers. The pattern that they support is a docker-hub image that uses SemVer in the label to contextualize their dependencies, so the nodeJS pattern is just one of many others that fall outside that paradigm.

A little bit more research…

After a quick Google, I went and found a few threads where people were cheering an alternative to dependabot for docker stuff, Renovate, by Mend.

I’d come across this before, but being a Github native I’d always just deferred to dependabot.

I had to install the app into my org, trust it with my test repo – but once it had been plumbed in and read my code.. It raised the PR that I wanted – a direct update of the sha for the node:20-alpine tag:

I can see why Dependabot is struggling; but if renovate is ahead of the game here then I may be rethinking what else it’s picking up on that Dependabot is missing…

What do you use to manage your dependencies? Is there another dependency bot that’s even better?

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.