Skip to content

A tag ending in a variable is updated and pinned, which breaks the reference #412

Closed

Description

@fniessink

Version

Update-time 0.0.44

Summary

An image tag can start with a version and end in a variable, such as java:1.1.0-${VARIANT}. Update-time reads the part before the variable as the whole tag. It then updates that part, appends a digest, and leaves the variable behind the digest. The reference it writes is invalid, so the build that uses it fails.

The README says Update-time leaves such a reference alone. It says Update-time ignores an image whose tag it cannot read, such as a tag through a {{ ... }} template or a ${VAR} variable substitution. That holds for a tag that consists of the variable alone, such as app:${VAR}. It does not hold for a tag that only ends in one.

Steps to reproduce

In a git repository, create these files:

Dockerfile:

FROM python:3.12-$VARIANT

docker-compose.yml:

services:
  cache:
    image: redis:7.2-${VARIANT}

Run update-time.

Expected

Both files stay as they are, as the README promises for a reference through a variable.

Actual

Update-time reports a new version for each reference:

INFO New version available for redis in docker-compose.yml:3: 8.10.1, published: 2026-09-19 06:06
INFO New version available for python in Dockerfile:1: 3.14.7, published: 2026-09-19 17:09

It rewrites both files:

FROM python:3.14.7@sha256:be8ccd085666c34273c9dc5607c9842f8b2e3116128aae45148ce164c07ce09d$VARIANT
    image: redis:8.10.1@sha256:8a1efc5f479551822b47424ccae982026b633f28818eab0387348120a61e10e2${VARIANT}

Docker substitutes the variable after the digest, so neither reference is valid.

The variable also hides the variant from the update. Update-time reads 7.2- as the tag 7.2 without a suffix, so it picks the newest tag without a suffix. That tag can name another variant than the one the variable selects. Here it also moved redis from 7.2 to 8.10.1, a new major version.

The same happens in the wild. TheAlgorithms/Java has this line in .devcontainer/Dockerfile:

FROM mcr.microsoft.com/vscode/devcontainers/java:1.1.0-${VARIANT}

A copy of that file named Dockerfile became:

FROM mcr.microsoft.com/vscode/devcontainers/java:25@sha256:da82e11bce1d8c01f36e1dc559f1aadaa28fcbd3e1356fd0bd9fa100f5b3d75d${VARIANT}

That repository's own file escaped only because Update-time does not scan hidden folders (see HIDDEN_DOCKERFILES.md).

Analysis

OPTIONALLY_TAGGED_IMAGE_REFERENCE in sources/oci.py reads a tag as [\d\w\.\-]*. It then requires the reference to end with the lookahead (?![\w\d\./:@-]). A $ is not in that class, so the reference ends at 1.1.0-. The comment above the pattern says a tag through a variable does not match. It considers app:${VAR} only, where the colon is followed by the $ itself.

YAML_IMAGE_REFERENCE builds on the same pattern. So the defect reaches the Dockerfile updater and the Docker Compose, Helm, CircleCI, and GitLab CI updaters. A probe of the patterns gives:

Reference OPTIONALLY_TAGGED_IMAGE_REFERENCE reads the tag IMAGE_REFERENCE reads the tag
java:1.1.0-${VARIANT} 1.1.0- 1.1.0-
java:1.1.0-$VARIANT 1.1.0- 1.1.0-

IMAGE_REFERENCE does not end in a lookahead at all. The devcontainer updater is its only user, and there a closing " follows the pattern, so a variable after the tag fails the match. The devcontainer image uses the optional pattern, also followed by a closing ".

Suggested fix

Let the reference end only where a reference can end. Adding $ to the lookahead stops the match at 1.1.0-, so the reference is passed over as a reference through a variable. A closing quote ends each reference in a devcontainer.json, so its patterns can stay as they are.

Two things are worth deciding beside the fix:

  1. Whether Update-time reports the skipped reference at DEBUG, as it reports an image the file does not pull. Today Update-time does not log a reference through a variable.
  2. How this relates to A Dockerfile's base image assembled from build arguments #357, which proposes resolving a Dockerfile's ARG defaults. Once A Dockerfile's base image assembled from build arguments #357 lands, a FROM like the one above resolves to a tag Update-time can read. Until then, the fix here keeps such a reference intact.

Documentation

Once the fix lands, the README is right about a tag through a variable. The fix gets a changelog entry.

Activity

  1. added theissue type on Oct 12, 2026
  2. self-assigned this
    on Oct 12, 2026
  3. added a commit that references this issue on Oct 13, 2026
    c94ca83
  4. added a commit that references this issue on Oct 13, 2026
    d0ab222
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions