Skip to content

fix(dind): qualify single-segment refs with tag (alpine:3.20)#71

Merged
luthermonson merged 1 commit into
mainfrom
fix/dind-qualify-tag-parse
May 22, 2026
Merged

fix(dind): qualify single-segment refs with tag (alpine:3.20)#71
luthermonson merged 1 commit into
mainfrom
fix/dind-qualify-tag-parse

Conversation

@luthermonson
Copy link
Copy Markdown
Contributor

Summary

qualifyDockerHubRef("alpine:3.20") returned the input unchanged, so containerd's resolver then tried to parse alpine:3.20 as a host:port reference and failed with:

parse "dummy://alpine:3.20": invalid port ":3.20" after host

That surfaced inside dind as:

Error response from daemon: image alpine:3.20 not found:
  failed to resolve reference "dummy://alpine:3.20"

What was wrong

The helper's host-detection looked at the first segment for . or : and bailed if either was present. The intent was "if you see gcr.io/foo (dot) or localhost:5000/foo (colon-port), trust the caller and pass through." But it also matched the tag colon in single-segment refs:

Input First segment .: ? Old result Correct
alpine alpine no docker.io/library/alpine docker.io/library/alpine
alpine:3.20 alpine:3.20 yes alpine:3.20 docker.io/library/alpine:3.20
myorg/img myorg no docker.io/myorg/img docker.io/myorg/img
localhost:5000/x localhost:5000 yes localhost:5000/x localhost:5000/x

The test in this package even documented the broken behavior as "a known limitation."

Fix

The disambiguator is the slash. If there's no slash, the whole string is a single-segment name with optional :tag or @digest and should always be qualified with docker.io/library/. If there's a slash, the part before it is a host candidate and the existing ./:/localhost check is correct.

i := strings.IndexByte(ref, '/')
if i < 0 {
    return "docker.io/library/" + ref  // alpine, alpine:3.20, alpine@sha256:...
}
first := ref[:i]
if strings.ContainsAny(first, ".:") || first == "localhost" {
    return ref  // gcr.io/x, localhost:5000/x, my.registry:443/owner/x
}
return "docker.io/" + ref  // myorg/myimage[:tag]

Test plan

  • TestQualifyDockerHubRef updated: alpine:latestdocker.io/library/alpine:latest, plus new cases for alpine:3.20 and alpine@sha256:abc123. All 12 sub-cases pass.
  • Full pkg/dind suite green (go test -tags containers_image_openpgp ./pkg/dind/...).
  • Live: trigger an ephpm CI job that does docker run alpine:3.20 inside dind and verify the image is pulled + container starts.

Out of scope (filed separately)

When docker run alpine:3.20 succeeds at the image-resolution step, the next failure mode is unable to upgrade to tcp, received 501 because dind doesn't implement POST /containers/{id}/attach (HTTP Upgrade endpoint used by docker run without -d). That's a separate fix tracked on the fix/dind-container-attach branch — both bugs are in the same Docker CLI invocation but have unrelated root causes.

qualifyDockerHubRef returned the input unchanged for "alpine:3.20"
because its host-detection heuristic looked at the first segment for
either "." or ":" and bailed if either was present — meant to catch
"localhost:5000/foo" and "gcr.io/bar", it also matched the tag colon
in "alpine:3.20".

Downstream, containerd's resolver wraps unqualified refs in "dummy://"
so url.Parse can split them, and url.Parse("dummy://alpine:3.20")
fails with `invalid port ":3.20" after host` — that's the error
showing up as "image alpine:3.20 not found" in `docker run` from dind.

Fix: use the slash as the disambiguator.
  - No slash → no path → single-segment name with optional tag/digest.
    Always qualifies with docker.io/library/. Covers "alpine",
    "alpine:3.20", "alpine@sha256:abc".
  - Slash present → segment before the slash is a host candidate, the
    existing ".:" or "localhost" check is correct for distinguishing
    "localhost:5000/foo" or "gcr.io/bar" from "myorg/myimage".

The test previously documented the broken behavior as a "known
limitation"; replace those assertions with the correct expected
values and add coverage for `alpine:3.20` and `alpine@sha256:...`.
@luthermonson luthermonson merged commit a2e10c1 into main May 22, 2026
4 checks passed
@luthermonson luthermonson deleted the fix/dind-qualify-tag-parse branch May 22, 2026 13:59
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