christoph ender's

blog

saturday the 4th of july, 2026

Monitoring docker image upgrades

Running containers also means keeping their images up to date. While it's quite easy to find out which Linux OS packages need upgrading, periodic checks for docker images aren't that easy, so I wrote a small Icinga/Nagios plugin which gives alerts when new images are available.

The script was written for Docker Compose. Since Docker Compose itself provides no simple way to determine whether a newer image exists, it is necessary to execute docker pull for the images being monitored. In order to not mess with the running configuration, it is therefore required to “pin” the current image in the corresponding docker-compose.yaml file, which can be achieved by substituting the tag for the image: with a specific @sha256: digest:

docker-compose.yaml
services:
  db:
    image: mariadb@sha256:628f228f0fd5913a2204386…
    x-upgrade-data:
      image-source-tag: mariadb:12

This will ensure that only the currently chosen image is run upon (re)starting containers, which wouldn't be the case if for example a latest tag would be used.

Also, the script needs to know for which tag the docker pull operation should be run. This is provided by the image-source-tag in a x-upgrade-data section. This might be <image-name>:latest for the latest image, but could also be a tag like 2.3 or similar, to ensure that we're running a specific minor version and are not doing a major version upgrade. In the example above, the docker pull will get the most current mariadb image for version 12 (please check the tag format for your images since the tag syntax is not fixed).

The script itself is shown below and also directly available from https://github.com/it-sys-ce/docker-compose-images-upgrade.

docker-compose-images-upgrade.sh
#!/bin/bash

# 2026-07-03 - christoph.ender@it-sys-ce.de - Version 1.0, requires "yq".


if [[ $# -ne 1 && $# -ne 4 ]]
then
  echo "Syntax:"
  echo "  ${0} <compose-dir>"
  echo "  ${0} <compose-dir> <registry> <registry-username> <registry-password>"
  exit 1
fi

if [[ $# -eq 4 ]]
then
  REGISTRY=${2}
  REGISTRY_USERNAME=${3}
  REGISTRY_PASSWORD=${4}

  docker login \
   -u ${REGISTRY_USERNAME} \
   -p ${REGISTRY_PASSWORD} \
   ${REGISTRY} >/dev/null 2>&1
fi

COMPOSE_DIR=${1}

CONTAINERS=$(docker compose \
  --project-directory "${COMPOSE_DIR}" ps --format "{{.Service}}\t{{.Name}}")

IMAGE_LIST=
NEW_IMAGE_LIST=
UPDATE_FOUND=0

while read -r CONTAINER_LINE
do
  IFS=$'\t' read -r SERVICE_NAME CONTAINER_NAME <<< "${CONTAINER_LINE}"

  # Parse image name
  IMAGE=$(docker inspect "${CONTAINER_NAME}" \
    --format "{{.Config.Image}}" | sed 's/:[^:]*$//' | sed 's/@[^@]*$//')

  IMAGE_SOURCE_TAG=$(docker compose --project-directory "${COMPOSE_DIR}" config \
    | yq -r .\"services\".\"${SERVICE_NAME}\".\"x-upgrade-data\".\"image-source-tag\")

  # Get hash of currently running container image.
  RUNNING_HASH=$(docker inspect "${CONTAINER_NAME}" --format "{{.Image}}")

  # Update docker images with latest image.
  docker pull "${IMAGE_SOURCE_TAG}" --quiet >/dev/null 2>&1

  # Get hash of latest image.
  LATEST_HASH=$(docker images --no-trunc --quiet ${IMAGE_SOURCE_TAG})
  LATEST_DIGEST=$(docker image inspect \
    --format '{{index .RepoDigests 0}}' ${LATEST_HASH})

  # Check if currently running image is not the latest.
  if [[ ${RUNNING_HASH} != ${LATEST_HASH} ]]
  then
    UPDATE_FOUND=1
    # Append to "new image" list.
    NEW_IMAGE_LIST="${NEW_IMAGE_LIST:+$NEW_IMAGE_LIST$'\n'}${IMAGE_SOURCE_TAG} - ${LATEST_DIGEST}"
  else
    # Append to "current image" list.
    IMAGE_LIST="${IMAGE_LIST:+$IMAGE_LIST$'\n'}${IMAGE_SOURCE_TAG} - ${LATEST_DIGEST}"
  fi
done <<<${CONTAINERS}



if [[ ${UPDATE_FOUND} -eq 0 ]]
then
  echo -e "OK - Using latest images in ${COMPOSE_DIR}.\n${IMAGE_LIST}"
  exit 0
else
  if [[ -n $IMAGE_LIST ]]
  then
    OUTPUT="\nUp to date images:\n${IMAGE_LIST}"
  fi

  OUTPUT="${OUTPUT}\nNew images available:\n${NEW_IMAGE_LIST}"

  echo -e "WARNING - New image(s) available for ${COMPOSE_DIR}.${OUTPUT}"
  exit 1
fi