From b840a7ab075efd52b73e41b11c2fbb43b80742a1 Mon Sep 17 00:00:00 2001 From: mehrdadbn9 Date: Thu, 12 Feb 2026 15:51:35 +0330 Subject: [PATCH 1/2] libimage: treat localhost/ images as local-only Images prefixed with "localhost/" (without a port number) are a naming convention for local-only images in Podman/Buildah. They should never attempt a network pull from a Docker registry. Previously, when building an image with "FROM localhost/base-toolbox:latest" in a Containerfile, podman would attempt to pull from docker://localhost/base-toolbox:latest, which fails because localhost is not a real Docker registry. This fix adds a check in copySingleImageFromRegistry to detect localhost/ prefixed image names and return the local image if found, or an error if not found, without ever attempting a network pull. Fixes: containers/podman#28038 Signed-off-by: mehrdadbn9 --- common/libimage/pull.go | 50 ++++++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 15 deletions(-) diff --git a/common/libimage/pull.go b/common/libimage/pull.go index 1183311f4c..2541be64ed 100644 --- a/common/libimage/pull.go +++ b/common/libimage/pull.go @@ -12,20 +12,20 @@ import ( "strings" "time" + "github.com/containers/common/pkg/config" + registryTransport "github.com/containers/image/v5/docker" + dockerArchiveTransport "github.com/containers/image/v5/docker/archive" + dockerDaemonTransport "github.com/containers/image/v5/docker/daemon" + "github.com/containers/image/v5/docker/reference" + ociArchiveTransport "github.com/containers/image/v5/oci/archive" + ociTransport "github.com/containers/image/v5/oci/layout" + "github.com/containers/image/v5/pkg/shortnames" + storageTransport "github.com/containers/image/v5/storage" + "github.com/containers/image/v5/transports/alltransports" + "github.com/containers/image/v5/types" + "github.com/containers/storage" ociSpec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/sirupsen/logrus" - "go.podman.io/common/pkg/config" - registryTransport "go.podman.io/image/v5/docker" - dockerArchiveTransport "go.podman.io/image/v5/docker/archive" - dockerDaemonTransport "go.podman.io/image/v5/docker/daemon" - "go.podman.io/image/v5/docker/reference" - ociArchiveTransport "go.podman.io/image/v5/oci/archive" - ociTransport "go.podman.io/image/v5/oci/layout" - "go.podman.io/image/v5/pkg/shortnames" - storageTransport "go.podman.io/image/v5/storage" - "go.podman.io/image/v5/transports/alltransports" - "go.podman.io/image/v5/types" - "go.podman.io/storage" ) // PullOptions allows for customizing image pulls. @@ -253,8 +253,8 @@ func (r *Runtime) copyFromDefault(ctx context.Context, ref types.ImageReference, storageName = imageName case ociTransport.Transport.Name(): - _, refName, ok := strings.Cut(ref.StringWithinTransport(), ":") - if !ok || refName == "" { + split := strings.SplitN(ref.StringWithinTransport(), ":", 2) + if len(split) == 1 || split[1] == "" { // Same trick as for the dir transport: we cannot use // the path to a directory as the name. storageName, err = getImageID(ctx, ref, nil) @@ -263,7 +263,7 @@ func (r *Runtime) copyFromDefault(ctx context.Context, ref types.ImageReference, } imageName = "sha256:" + storageName[1:] } else { // If the OCI-reference includes an image reference, use it - storageName = refName + storageName = split[1] imageName = storageName } @@ -501,6 +501,18 @@ func (r *Runtime) copySingleImageFromRegistry(ctx context.Context, imageName str } } + // Images prefixed with "localhost/" (without a port) are local-only by + // convention and cannot be pulled from a remote registry. Return the + // local image if available, or an error if not found, without ever + // attempting a network pull (see containers/podman/issues/28038). + if isLocalhostImage(imageName) { + if localImage != nil { + logrus.Debugf("Image %s is a localhost image, returning local image", imageName) + return localImage, nil + } + return nil, fmt.Errorf("%s: %w", imageName, storage.ErrImageUnknown) + } + customPlatform := len(options.Architecture)+len(options.OS)+len(options.Variant) > 0 if customPlatform && pullPolicy != config.PullPolicyAlways && pullPolicy != config.PullPolicyNever { // Unless the pull policy is always/never, we must @@ -659,3 +671,11 @@ func (r *Runtime) copySingleImageFromRegistry(ctx context.Context, imageName str return nil, resolved.FormatPullErrors(pullErrors) } + +// isLocalhostImage returns true if the image name refers to a localhost-only +// image (i.e., prefixed with "localhost/" but without a port number). +// Images with "localhost:PORT/" are actual registry references and should +// not be treated as local-only. +func isLocalhostImage(name string) bool { + return strings.HasPrefix(name, "localhost/") +} From db5d321384a32bf137ce3af088da0817a4cd9580 Mon Sep 17 00:00:00 2001 From: mehrdadbn9 Date: Thu, 12 Feb 2026 17:14:59 +0330 Subject: [PATCH 2/2] libimage: treat localhost/ images as local-only Images prefixed with "localhost/" (without a port number) are a naming convention for local-only images in Podman/Buildah. They should never attempt a network pull from a Docker registry. Previously, when building an image with "FROM localhost/base-toolbox:latest" in a Containerfile, podman would attempt to pull from docker://localhost/base-toolbox:latest, which fails because localhost is not a real Docker registry. This fix adds a check in copySingleImageFromRegistry to detect localhost/ prefixed image names and return the local image if found, or an error if not found, without ever attempting a network pull. Fixes: containers/podman#28038 Signed-off-by: mehrdadbn9 --- common/libimage/pull.go | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/common/libimage/pull.go b/common/libimage/pull.go index 2541be64ed..04b76afc19 100644 --- a/common/libimage/pull.go +++ b/common/libimage/pull.go @@ -12,20 +12,20 @@ import ( "strings" "time" - "github.com/containers/common/pkg/config" - registryTransport "github.com/containers/image/v5/docker" - dockerArchiveTransport "github.com/containers/image/v5/docker/archive" - dockerDaemonTransport "github.com/containers/image/v5/docker/daemon" - "github.com/containers/image/v5/docker/reference" - ociArchiveTransport "github.com/containers/image/v5/oci/archive" - ociTransport "github.com/containers/image/v5/oci/layout" - "github.com/containers/image/v5/pkg/shortnames" - storageTransport "github.com/containers/image/v5/storage" - "github.com/containers/image/v5/transports/alltransports" - "github.com/containers/image/v5/types" - "github.com/containers/storage" ociSpec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/sirupsen/logrus" + "go.podman.io/common/pkg/config" + registryTransport "go.podman.io/image/v5/docker" + dockerArchiveTransport "go.podman.io/image/v5/docker/archive" + dockerDaemonTransport "go.podman.io/image/v5/docker/daemon" + "go.podman.io/image/v5/docker/reference" + ociArchiveTransport "go.podman.io/image/v5/oci/archive" + ociTransport "go.podman.io/image/v5/oci/layout" + "go.podman.io/image/v5/pkg/shortnames" + storageTransport "go.podman.io/image/v5/storage" + "go.podman.io/image/v5/transports/alltransports" + "go.podman.io/image/v5/types" + "go.podman.io/storage" ) // PullOptions allows for customizing image pulls. @@ -253,8 +253,8 @@ func (r *Runtime) copyFromDefault(ctx context.Context, ref types.ImageReference, storageName = imageName case ociTransport.Transport.Name(): - split := strings.SplitN(ref.StringWithinTransport(), ":", 2) - if len(split) == 1 || split[1] == "" { + _, refName, ok := strings.Cut(ref.StringWithinTransport(), ":") + if !ok || refName == "" { // Same trick as for the dir transport: we cannot use // the path to a directory as the name. storageName, err = getImageID(ctx, ref, nil) @@ -263,7 +263,7 @@ func (r *Runtime) copyFromDefault(ctx context.Context, ref types.ImageReference, } imageName = "sha256:" + storageName[1:] } else { // If the OCI-reference includes an image reference, use it - storageName = split[1] + storageName = refName imageName = storageName }