Skip to content

[Bug]: Auto-update never runs up -d: the digest check reads container images, which pull can't change #132

Description

@KogasaPls

Summary

Scheduled auto-update never recreates containers. Every run logs No updates for stack '<name>', even when the pull actually fetched a newer image, so stacks quietly stay on their old images forever and the superseded images pile up in the Docker tab as "orphan images" (dangling but in use).

Version: 2026.07.02, Docker Compose v5.1.2

Root cause

scripts/compose_autoupdate.sh decides whether to run up -d by comparing image digests before and after the pull:

get_image_digests() {
  docker compose "${project_dir_args[@]}" "${compose_file_args[@]}" "${env_file_args[@]}" -p "$PROJECT_NAME" images -q 2>/dev/null | while read -r img_id; do
    if [ -n "$img_id" ]; then
      docker inspect --format='{{index .RepoDigests 0}}' "$img_id" 2>/dev/null || echo "$img_id"
    fi
  done | sort
}

OLD_DIGESTS=$(get_image_digests || true)
... docker compose ... pull --ignore-buildable ...
NEW_DIGESTS=$(get_image_digests || true)

if [ "$OLD_DIGESTS" != "$NEW_DIGESTS" ]; then
  ... up -d ...

The problem: docker compose images lists the images of the existing containers, and a pull never touches containers. So OLD_DIGESTS and NEW_DIGESTS are always equal and the up -d branch is unreachable. The pull still moves the tag, which is what strands the running containers on untagged images.

Reproduction

  1. Take any stack with auto-update enabled whose service uses a mutable tag (e.g. nginx:mainline-alpine).
  2. Simulate an available update with docker tag <old-image-id> nginx:mainline-alpine, or just wait for upstream to publish a new image.
  3. Run compose_autoupdate.sh for the stack, or wait for the schedule.
  4. The pull repoints the tag to the newer image, the script logs No updates for stack '<name>', and the container keeps running the old image, which is now dangling.

Suggested fix

Read the digests from the compose config's image references instead of from the containers:

get_image_digests() {
  docker compose "${project_dir_args[@]}" "${compose_file_args[@]}" "${env_file_args[@]}" -p "$PROJECT_NAME" config --images 2>/dev/null | while read -r img_ref; do
    if [ -n "$img_ref" ]; then
      docker image inspect --format='{{index .RepoDigests 0}}' "$img_ref" 2>/dev/null || echo "$img_ref"
    fi
  done | sort
}

Two changes in there:

  • images -qconfig --images: digests now come from whatever the tag points at, which is exactly what pull updates. Before the first-ever pull of an image the inspect fails and falls back to echoing the ref, so a fresh pull also registers as a change, which is what you want. Locally built services have no RepoDigests, fall back to the (stable) ref, and get skipped, consistent with pull --ignore-buildable.
  • docker inspectdocker image inspect: config --images yields names, and a bare docker inspect <name> can collide with a container of the same name.

I've been running this patch and it works: with a stack whose tag deliberately pointed at an older image, the patched script pulled, saw the digest change, ran up -d, and recreated the stack's containers onto the current images. When nothing has changed it still reports No updates.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions