Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 20 additions & 8 deletions pkg/dind/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -338,20 +338,32 @@ func tailHex(s string) string {
// qualifyDockerHubRef ensures a reference carries an explicit registry
// host. containerd's resolver treats the first path segment of an
// unqualified reference as the hostname (so "ephpm/ephemerd:tag" → host
// "ephpm" → DNS lookup fails). Docker CLI conventions default unqualified
// refs to docker.io, prepending "library/" for single-segment names like
// "ephpm" → DNS lookup fails; "alpine:3.20" → host "alpine", port "3.20"
// → url.Parse rejects). Docker CLI conventions default unqualified refs
// to docker.io, prepending "library/" for single-segment names like
// "ubuntu". This helper applies the same rule.
//
// The slash is the disambiguator between a "host[:port]/path" reference
// and a "name[:tag]" reference. With a slash, the part before it is a
// host candidate and a dot or colon there means we've got a real
// registry host (gcr.io, localhost:5000, my-registry.com:443, etc.).
// Without a slash, there's no path and the whole string is a single-
// segment image name with an optional tag — always docker.io/library/.
func qualifyDockerHubRef(ref string) string {
first := ref
if i := strings.IndexByte(ref, '/'); i >= 0 {
first = ref[:i]
i := strings.IndexByte(ref, '/')
if i < 0 {
// No slash → single-segment name (possibly with tag/digest).
// Always qualifies with docker.io/library/. This covers the
// `alpine`, `alpine:3.20`, `alpine@sha256:...` cases.
return "docker.io/library/" + ref
}
first := ref[:i]
if strings.ContainsAny(first, ".:") || first == "localhost" {
// Looks like a real host (has a dot, or has a port, or is
// literally "localhost") — already qualified.
return ref
}
if !strings.Contains(ref, "/") {
return "docker.io/library/" + ref
}
// Multi-segment name without a host (e.g. "myorg/myimage[:tag]").
return "docker.io/" + ref
}

Expand Down
13 changes: 8 additions & 5 deletions pkg/dind/registry_helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,14 @@ func TestQualifyDockerHubRef(t *testing.T) {
}{
// Single-segment unqualified names get library/ prepended.
{"alpine", "docker.io/library/alpine"},
// Single-segment with tag — qualifyDockerHubRef sees the ":" and
// thinks "alpine:latest" is already a host, so it passes through
// unchanged. This is a known limitation; passing "alpine" without
// a tag is the supported form when callers want library/ added.
{"alpine:latest", "alpine:latest"},
// Single-segment WITH tag: the previous implementation bailed on the
// tag colon and passed it through unchanged, which made containerd's
// resolver try to parse "alpine" as a host and "3.20" as a port
// number. The disambiguator is the slash — no slash means no path,
// so the colon must be a tag separator.
{"alpine:latest", "docker.io/library/alpine:latest"},
{"alpine:3.20", "docker.io/library/alpine:3.20"},
{"alpine@sha256:abc123", "docker.io/library/alpine@sha256:abc123"},
// Two-segment names get docker.io/ prepended.
{"myorg/myimage", "docker.io/myorg/myimage"},
{"myorg/myimage:tag", "docker.io/myorg/myimage:tag"},
Expand Down