Skip to content

fix(docker): never invent a registry host for an image - #2654

Merged
DeusData merged 1 commit into
mainfrom
fix/image-unknown-registry
Oct 16, 2026
Merged

DeusData merged 1 commit into
mainfrom
fix/image-unknown-registry

Conversation

@DeusData

@DeusData DeusData commented Oct 16, 2026 •

Copy link
Copy Markdown
Owner

Summary

An image reference that names no registry host was stored under an invented one. The graph then asserted a registry the code never names:

Written Stored before Stored now (registry)
FROM nginx:1.27 docker.io/library/nginx nginx (unknown)
image: acme/web:1 docker.io/acme/web acme/web (unknown)
docker build -t myapp:dev svc, skaffold image: api docker.io/library/myapp, docker.io/library/api myapp, api (unknown)
FROM docker.io/redis:7 docker.io/library/redis unchanged (docker.io)
FROM index.docker.io/acme/tool docker.io/acme/tool unchanged (docker.io)
FROM ghcr.io/acme/app@sha256:… ghcr.io/acme/app unchanged (ghcr.io)
FROM localhost:5000/team/x localhost:5000/team/x unchanged (localhost:5000)

docker.io is only Docker's default:

  • Podman and CRI-O resolve short names through a search list.
  • Mirrors rewrite them.
  • A local build tag may exist on no registry at all.

Change

  • dk_normalize (src/pipeline/pass_docker.c):
    • A reference without a host keeps its repository as written, and its registry is "unknown".
    • A stated host is kept as written. Docker Hub's spellings become docker.io, with library/ for an official one-segment name.
  • Image nodes carry a new registry property, including the per-component Images of a repository built from several Dockerfiles.
  • No change in reach: every source still makes the same edges. These sources are FROM and COPY --from, compose, K8s containers, and shell and skaffold build tags.
  • FROM nginx and image: docker.io/library/nginx are now two Image nodes. Relating them would state the host again, so the graph does not.
  • CBM_INDEX_FORMAT_VERSION 2 → 3. The Image QN changes from __image__docker.io/library/nginx to __image__nginx. Refreshed in place, an older index would give only touched files the new identity, and one graph would hold both forms of an image. Shown on a fixture: an unchanged K8s Deployment kept docker.io/library/nginx while the edited Dockerfile got the new name.

Release notes: every existing index is rebuilt once on its first index after upgrading. The response reports "format_migration": true.

Smart CI: this repository's own graph changes, because its Dockerfiles' Image names change. The test-impact producer's graph-content digest (store_content_digest.c) changes with it. The first producer run after this lands is a full refresh, so PRs may get no admitted coverage for about an hour.

Proof on real input

17 pinned OSS infra corpora (KKP, gardener, deckhouse, cluster-api, k8s.io, flux, home-ops, microservices-demo, otel-demo, prometheus charts, eks-blueprints, cloud-foundation-fabric, terragrunt, voting-app, awesome-compose, kube-hetzner, kubespray), indexed with a build of ec6427c3 and with this branch:

before after
Image nodes 316 319
named docker.io/… 135 20
… of which the host was invented 118 0 (now registry: unknown)
edges touching Image nodes, per corpus identical in all 17
images written both with and without docker.io 3, all in opentelemetry-demo: alpine, golang, python

Upgrade path: a fixture was indexed with the ec6427c3 build (format 2), then once with this branch. The response reported format_migration: true, every Image had its new identity with all its edges, and user_version was 3.

Tests

  • New probe_image_without_registry_host_stays_unknown (grammar_probe_f) covers every source: Dockerfile FROM (one- and two-segment, docker.io, index.docker.io, ghcr.io, localhost:5000), a K8s container, a compose service, and a shell build tag. It asserts each Image's name and registry. On ec6427c3 every host-less reference reads docker.io/… with no registry.
  • New index_format_v2_images_rebuild_without_invented_host (index_format) rewrites an index into the format-2 image shape, stamps format 2, and re-indexes. It fails without the bump: the index is not routed through the rebuild.
  • Seven existing grammar_probe_f expectations change from docker.io/… to the name as written. Each change concerns a host-less fixture reference. Every stated host, such as docker.io/bitnami/kubectl, gcr.io, ghcr.io, quay.io and registry.example.com:5000, is unchanged.
  • Two incremental tests in pipeline_semantic_manifest_repro queried their host-less fixtures (acme/web-old, acme/demo:*-b) under the docker.io names. Their queries now use the names as written. The routes and counts they assert are unchanged.

Verification

  • macOS, ASan + UBSan test runner, whole suite on the final tree: 9707 passed, 0 failed. The 11 skipped tests are platform-only (Linux user namespaces, Windows ACL, Job Object and PowerShell probes). No sanitizer reports.
  • clang-format (Homebrew LLVM) and cppcheck 2.20 (the CI version) are clean on the changed sources.
  • Skipped locally: the Linux and Windows legs. The change is platform-neutral string and SQL code; PR CI runs both.

Found, not changed here

  • Default values still name images: ${IMAGE:-myapp} in a script and Dockerfile ARG defaults are expanded. The result no longer gets an invented host, but a build that overrides the default makes a different image.
  • Helm Chart nodes have no outgoing edges unless the chart declares dependencies. Nothing links a chart to its templates' resources, its values or its images. That design follows separately.

An image reference that names no registry host was stored under an
invented one: `FROM nginx` became docker.io/library/nginx, `image:
acme/web` docker.io/acme/web, and a local `docker build -t myapp` or a
skaffold artifact `image: api` docker.io/library/myapp. docker.io is only
Docker's default. Podman and CRI-O resolve a short name through a search
list, mirrors rewrite it, and a local build tag may exist on no registry
at all. The graph stated a host the code never names.

An Image is now the repository as written, tag and digest dropped, with
a `registry` property: the host the reference names, else "unknown". A
stated host is kept as written; Docker Hub's spellings (docker.io,
index.docker.io, registry-1.docker.io) are docker.io, with library/ for
an official one-segment name. This holds for every source: FROM and
COPY --from, compose services, K8s containers, shell and skaffold build
tags, and the per-component Images of a repository built from several
Dockerfiles.

`FROM nginx` and `image: docker.io/library/nginx` are now two Image
nodes. Relating them would state the host again, so the graph does not.

The Image QN changes (__image__nginx instead of
__image__docker.io/library/nginx), so CBM_INDEX_FORMAT_VERSION moves to
3. Refreshed in place, an older index would give only touched files the
new identity and hold both forms of one image; every existing index is
rebuilt once on its first index after upgrading.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
@DeusData
DeusData merged commit ff829cb into main Oct 16, 2026
29 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant