Background:

A year ago, around Christmas 2022, I saw people on reddit talking about why certain docker update services downloaded the latest image of every container to compare if it was an update or not.. Why it wasn’t possible to check first and download after.

This issue is mainly due to how docker images work - there’s no clear release numbering format, no requirements on how new images are versioned, no easy way to compare if your current image is the same version as the newest.

There has to be a way

So I spent Christmas pondering on how I could achieve this. I started curling, json grabbing, comparing different fields, I tried comparing the docker image inspect creation date with something in the registry or on docker hub, or trying to grab version numbers - but what if you run :latest on your container? Then you don’t know the number. Nothing seemed straight forward and working no matter where the images were hosted.

Then I stumbled upon regctl Client interface for the registry API.
I started comparing fields from the registry with the fields in the images, tried to find a safe way to know if an image had updates or not.

The digest - the key to victory

Every container image has something called a digest, a unique immutable (cannot be changed) checksum hash which can identify the specific image.

A local image has its digest in its details - a remote image in the registry has it’s digest which could be read with the help of regctl. Now I could compare if my local image is the same as the one in the registry! If it isn’t, that means there’s an update available and I can choose to update without having the download the image prior to my decision.

The first iteration of dockcheck.sh

So at first - my goal was to just check if a container had an update available. Let’s use homer as our example:

#!/bin/bash

IMGNAME=$(docker inspect $1 --format='{{.Config.Image}}')
IMGHASH=$( docker image inspect $IMGNAME --format '{{.RepoDigests}}' | sed -e 's/.*sha256/sha256/' -e 's/\]$//' )
REPOHASH=$(./regctl image digest --list $IMGNAME)

echo "ImgHash: $IMGHASH"
echo "RepoHash: $REPOHASH"
echo ""

if [[ "$IMGHASH" != "$REPOHASH" ]] ; then echo "Updates available: $IMGNAME" ; fi
if [[ "$IMGHASH" == "$REPOHASH" ]] ; then echo "Already latest version: $IMGNAME" ; fi

Running it ./dockcheck.sh homer would give something like:

ImgHash: sha256:2999479690d369ce538b2adc7731ff5b8bfe1955280ba03de4ab8966041033a3
RepoHash: sha256:2999479690d369ce538b2adc7731ff5b8bfe1955280ba03de4ab8966041033a3

Already latest version: b4bz/homer

Success!

The second iteration

While I proved that it worked, it was pretty tedious doing them one by one in a environment with 20+ containers. So I made a simple loop to just print each running container image - if it got updates or not.
The output wasn’t pretty or clean, just an unordered list:

qmcgaw/gluetun already latest.
b4bz/homer got updates.
postgres:14 already latest.

Some refining:

for i in $(docker ps --format '{{.Image}},{{.Names}}') 
do
  RepoUrl=$( echo "$i" | cut -d, -f1)
  ContName=$( echo "$i" | cut -d, -f2)
  LocalHash=$(docker image inspect $RepoUrl --format '{{.RepoDigests}}' | sed -e 's/.*sha256/sha256/' -e 's/\]$//')
  RegHash=$(regctl image digest --list $RepoUrl)
  if [[ "$LocalHash" != "$RegHash" ]] ; then
    GotUpdates+=("$ContName")
  else
    NoUpdates+=("$ContName")
  fi
done

### List what containers got updates or not, color coded headers
if [ ! -z $GotUpdates ] ; then
  printf "\n\033[31;1mContainers with updates available:\033[0m\n"
  printf "%s\n" "${GotUpdates[@]}"
fi
if [ ! -z $NoUpdates ] ; then
  printf "\n\033[32;1mContainers on latest version:\033[0m\n"
  printf "%s\n" "${NoUpdates[@]}"
fi

I added the updates/no updates to arrays and then printed them when done:

Containers with updates available: # color dosnt show here
b4bz/homer got updates.

Containers on latest version: # color dosnt show here
qmcgaw/gluetun already latest.
postgres:14 already latest.

I decided it was probably time to start posting about my findings.
Had to do some more work on it first, created another loop that looped through the list of containers with updates and actually updated them too - something like:

  for i in "${GotUpdates[@]}"
  do 
    # Check what compose-type is installed:
    if docker compose &> /dev/null ; then DockerBin="docker compose" ; else DockerBin="docker-compose" ; fi
    ContPath=$(docker inspect $i --format '{{ index .Config.Labels "com.docker.compose.project.working_dir"}}')
    $DockerBin -f "$ContPath/docker-compose.yml" pull 
    $DockerBin -f "$ContPath/docker-compose.yml" up -d
  done

And that became the first commit of some shy 68 lines of code.

One year later

example.gif

Now almost exactly one year later, 242 commits and a bunch of features have been added and sitting just above 300 lines of code. I’m humbled by the 550 Stars and hundreds of visitors each week!
The most recent update is to add the option for external notifications, with the possibility for users to make their own custom notify.sh add-on and/or suggest more templates.

$ ./dockcheck.sh -h
 Syntax:     dockcheck.sh [OPTION] [part of name to filter]
 Example:    dockcheck.sh -y -d 10 -e nextcloud,heimdall
 
 Options:
 -a|y   Automatic updates, without interaction.
 -d N   Only update to new images that are N+ days old. Lists too recent with +prefix and age. 2xSlower.
 -e X   Exclude containers, separated by comma.
 -h     Print this Help.
 -i     Inform - send a preconfigured notification.
 -m     Monochrome mode, no printf color codes.
 -n     No updates, only checking availability.
 -p     Auto-Prune dangling images after update.
 -r     Allow updating images for docker run, wont update the container.
 -s     Include stopped containers in the check. (Logic: docker ps -a)

I’ve been thinking numerous times that “Well now it’s done.” but luckily users publish PRs, feature requests or issues I can continue with! Find it here mag37/dockcheck