docker/compose · error

unsupported protocol for address: %s

Error message

unsupported protocol for address: %s

What it means

memnet.DialEndpoint only accepts endpoints prefixed with unix:// or npipe://; anything else (tcp://, http://, a bare path, an empty string, or a typo like unix:/ or npipes://) falls through to this error before any dial is attempted. It is a strict, fail-fast contract: memnet exists to talk over local sockets, not TCP.

Source

Thrown at internal/memnet/conn.go:33

*/

package memnet

import (
	"context"
	"fmt"
	"net"
	"strings"
)

func DialEndpoint(ctx context.Context, endpoint string) (net.Conn, error) {
	if addr, ok := strings.CutPrefix(endpoint, "unix://"); ok {
		return Dial(ctx, "unix", addr)
	}
	if addr, ok := strings.CutPrefix(endpoint, "npipe://"); ok {
		return Dial(ctx, "npipe", addr)
	}
	return nil, fmt.Errorf("unsupported protocol for address: %s", endpoint)
}

func Dial(ctx context.Context, network, addr string) (net.Conn, error) {
	var d net.Dialer
	switch network {
	case "unix":
		if err := validateSocketPath(addr); err != nil {
			return nil, err
		}
		return d.DialContext(ctx, "unix", addr)
	case "npipe":
		// N.B. this will return an error on non-Windows
		return dialNamedPipe(ctx, addr)
	default:
		return nil, fmt.Errorf("unsupported network: %s", network)
	}
}

View on GitHub (pinned to ddc4b044b6)

Solutions

  1. Prefix the value correctly: unix:///var/run/docker.sock (scheme + absolute path) or npipe:////./pipe/docker_engine on Windows.
  2. Trim whitespace and validate the endpoint string before calling DialEndpoint.
  3. If you need TCP, use net.Dialer.DialContext("tcp", addr) instead of memnet.DialEndpoint — memnet deliberately rejects it.
  4. Normalize at the config boundary so only validated endpoints reach the dialer.

Example fix

// before
conn, err := memnet.DialEndpoint(ctx, os.Getenv("DOCKER_HOST")) // may be "tcp://..." or bare path

// after
ep := strings.TrimSpace(endpoint)
if !strings.HasPrefix(ep, "unix://") && !strings.HasPrefix(ep, "npipe://") {
    ep = "unix://" + ep // bare socket path
}
conn, err := memnet.DialEndpoint(ctx, ep)
Defensive patterns

Strategy: validation

Validate before calling

func validMemnetEndpoint(ep string) bool {
    return strings.HasPrefix(ep, "unix://") || strings.HasPrefix(ep, "npipe://")
}
if !validMemnetEndpoint(endpoint) {
    return fmt.Errorf("endpoint %q must be unix:// or npipe://", endpoint)
}

Type guard

func isUnixEndpoint(ep string) bool { return strings.HasPrefix(ep, "unix://") }
func isNpipeEndpoint(ep string) bool { return strings.HasPrefix(ep, "npipe://") }

Try / catch

conn, err := memnet.DialEndpoint(ctx, ep)
if err != nil {
    return fmt.Errorf("dial %s: %w", ep, err) // surface which endpoint was rejected
}

Prevention

When it happens

Trigger: Calling memnet.DialEndpoint(ctx, endpoint) with an endpoint string that lacks an exact unix:// or npipe:// prefix — e.g. "/var/run/docker.sock", "tcp://127.0.0.1:2375", "unix:///var/run/x.sock" is fine but "UNIX://..." (case) is not (CutPrefix is case-sensitive).

Common situations: Passing DOCKER_HOST-style values straight through without normalizing (docker uses unix:///path with three slashes and also bare paths); config files storing "tcp://" endpoints fed to a socket-only dialer; empty endpoint after failed config parsing; trailing whitespace breaking the prefix match.

Related errors


AI-assisted analysis of docker/compose@ddc4b044b6 (2026-08-15). Data as JSON: /api/errors/7befb9221fb5d45f. Report an issue: GitHub.