Skip to content

Read image publication dates outside Docker Hub #254

Description

@fniessink

The problem

The cooldown and the staleness check reach Docker Hub images only. The README gives the same reason under both: other registries "expose no publication date, so images there are updated without a cooldown, and a cooldown marker on one of them is reported as redundant." An image on ghcr.io or mcr.microsoft.com is therefore adopted the moment a new tag appears, however fresh, and is never reported as stale however long its tag has sat unrebuilt.

An OCI registry does expose a date: the image config blob carries a created timestamp. Probed 2026-08-15, using Update-time's own registry authentication (oci._auth_headers, which performs the WWW-Authenticate challenge) and following the index manifest to an image manifest and then to its config blob:

Image created
ghcr.io/astral-sh/uv:0.9.7 2025-10-30T21:26:01Z
mcr.microsoft.com/dotnet/runtime:8.0 2026-08-10T16:56:43Z
gcr.io/distroless/static-debian12:latest 1970-01-01T00:00:00Z

Two of the three are the dates you would expect, and the third is the constraint this issue has to solve rather than a reason to abandon it.

The design constraint

created records when the image was built, not when it was pushed, and a reproducible build fixes it: distroless reports the Unix epoch. Read naively, that image is more than fifty years stale and past every cooldown that could ever be set — the staleness check would warn about the most actively maintained base image in the table above. Docker Hub's push date has no such failure mode, because the registry stamps it rather than the build.

So the date needs a credibility test before it can drive either check, and a date that fails it has to mean "unknown" rather than "very old" — which is what Update-time already does for a reference whose source reports no date at all.

The cost

Reading the date costs requests. Resolving a digest today fetches one manifest; a date needs the index manifest, one image manifest named by it, and the config blob, so two more requests per tag examined. The token is already fetched for the digest.

The cooldown multiplies that. It is not asked once per image but once per candidate tag, since Update-time walks candidates from the newest down until one is past the cooldown. On Docker Hub this is free, because the tag listing carries every tag's push date in a single response. Outside Docker Hub each candidate examined would cost its own manifest and blob, and an image whose newest tags are all within the cooldown window pays that for each of them.

Scope

  • Read the created date for an image on any OCI registry, and feed it to the cooldown and the staleness check that Docker Hub images already get.
  • Decide what makes such a date credible, and treat one that fails that test as no date at all, leaving the image updated without a cooldown and unreported for staleness, exactly as today.
  • Stop reporting a stale or cooldown marker on an image outside Docker Hub as redundant, since the scope would then hold something back.

Out of scope

  • Docker Hub images, which already answer both questions from the tag listing in one request.
  • CircleCI machine-executor images, which no registry serves, so both scopes stay redundant there.

Documentation

  • The per-type "Cooldown" and "Stale dependencies" answers for Docker images, both of which name Docker Hub as the limit.
  • The Docker-image rows in the dependency-type tables under "Cooldown" and "Stale dependencies".
  • "Setting a staleness threshold" and "Setting a cooldown period", which name an image on a registry other than Docker Hub as one of the three kinds of reference whose marker is reported as holding nothing back, and "Redundant markers", which points at that list.
  • A changelog entry under [Unreleased].

Open questions

  1. What test makes a created date credible? Treating the epoch as unknown covers distroless, but a build that sets SOURCE_DATE_EPOCH to its commit date produces a plausible-looking date that is equally not a publication date.
  2. Is the extra manifest and blob per candidate tag acceptable for the cooldown, or should the cooldown outside Docker Hub be applied to the chosen tag alone — adopting it or holding it back, rather than walking down to the newest eligible one?
  3. When the config blob carries no created, is the manifest's org.opencontainers.image.created annotation worth falling back to, or does it carry the same reproducible-build caveat?

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