docker/cli · error

invalid proto, expected tcp

Error message

invalid proto, expected tcp: %s

What it means

ParseTCPAddr rejects a tcp address that, after stripping the tcp:// prefix, still contains '://' (a nested scheme) or is empty in a way that is not the special-case bare 'tcp://'. It enforces that exactly one tcp scheme is present and that there is a usable remainder.

Solutions

  1. Use a single tcp scheme: tcp://host:port.
  2. Remove any nested '://' from the address.
  3. For the default port, use tcp://host or tcp://:2375 rather than nesting.
  4. Validate the address contains exactly one '://' before ParseTCPAddr.

Example fix

# before
docker -H tcp://unix:///var/run/docker.sock ps

# after
docker -H tcp://127.0.0.1:2375 ps
Defensive patterns

Strategy: validation

Validate before calling

// Ensure a tcp address has exactly one tcp scheme and a usable remainder.
func validTCPHost(s string) error {
    if strings.Count(s, "://") != 1 || !strings.HasPrefix(s, "tcp://") {
        return fmt.Errorf("expected a single tcp:// scheme in %q", s)
    }
    if strings.TrimPrefix(s, "tcp://") == "" {
        return fmt.Errorf("empty tcp address in %q", s)
    }
    return nil
}

Prevention

When it happens

Trigger: -H tcp:// with a nested scheme (tcp://unix://x) or a value that reduces to empty after the prefix in an unexpected form, e.g. tcp:// followed by another scheme token.

Common situations: Mixing protocols in one -H value; malformed -H from templating; accidentally prefixing an already-prefixed address.

Related errors


AI-assisted analysis of docker/cli@4f84911bfe (2026-08-07). Data as JSON: /api/errors/81a5bf1163fad978. Report an issue: GitHub.

Appendix: source

Thrown at opts/hosts.go:105

	}
	if addr == "" {
		addr = defaultAddr
	}
	return fmt.Sprintf("%s://%s", proto, addr), nil
}

// ParseTCPAddr parses and validates that the specified address is a valid TCP
// address. It returns a formatted TCP address, either using the address parsed
// from tryAddr, or the contents of defaultAddr if tryAddr is a blank string.
// tryAddr is expected to have already been Trim()'d
// defaultAddr must be in the full `tcp://host:port` form
func ParseTCPAddr(tryAddr string, defaultAddr string) (string, error) {
	if tryAddr == "" || tryAddr == "tcp://" {
		return defaultAddr, nil
	}
	addr := strings.TrimPrefix(tryAddr, "tcp://")
	if strings.Contains(addr, "://") || addr == "" {
		return "", fmt.Errorf("invalid proto, expected tcp: %s", tryAddr)
	}

	defaultAddr = strings.TrimPrefix(defaultAddr, "tcp://")
	defaultHost, defaultPort, err := net.SplitHostPort(defaultAddr)
	if err != nil {
		return "", err
	}
	// url.Parse fails for trailing colon on IPv6 brackets on Go 1.5, but
	// not 1.4. See https://github.com/golang/go/issues/12200 and
	// https://github.com/golang/go/issues/6530.
	if strings.HasSuffix(addr, "]:") {
		addr += defaultPort
	}

	u, err := url.Parse("tcp://" + addr)
	if err != nil {
		return "", err
	}

View on GitHub (pinned to 4f84911bfe)