docker/cli · error

links are only supported for user-defined networks

Error message

links are only supported for user-defined networks

What it means

Thrown by parseNetworkAttachmentOpt when links are supplied for a non-user-defined network. Legacy --link semantics (name aliasing) only work on user-defined networks in the modern code path; the default bridge uses legacy links differently and host/none/container: do not support them at all, so the CLI rejects the combination early.

Solutions

  1. Move the linked containers onto a user-defined network where DNS service discovery replaces --link entirely.
  2. If staying on host/none, remove the --link flags and use direct hostnames/IPs.
  3. On the default bridge, only legacy links are supported; switch to a user-defined bridge to use the modern link semantics.

Example fix

// before
docker run --network host --link db:db alpine

// after
docker network create appnet
docker run --network appnet --link db:db alpine
Defensive patterns

Strategy: validation

Validate before calling

func linksAllowed(networkMode string) bool {
    return container.NetworkMode(networkMode).IsUserDefined()
}

for net, links := range desiredLinks {
    if len(links) > 0 && !linksAllowed(net) {
        return fmt.Errorf("links are only supported for user-defined networks: %s", net)
    }
}

Prevention

When it happens

Trigger: `docker run --network host --link foo:bar alpine` or `--network none --link foo`. Also via advanced notation `--network host:link=foo:bar`. The IsUserDefined() check at opts.go:863 returns false and ep.Links is non-empty.

Common situations: Migrating from default-bridge --link usage to host networking while keeping the --link flag. Misconfigured compose that pairs links with external/host networks. Stale legacy scripts after switching network modes.

Related errors


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

Appendix: source

Thrown at cli/command/container/opts.go:868

	if copts.macAddress != "" {
		n.MacAddress = copts.macAddress
	}
	if copts.linkLocalIPs.Len() > 0 {
		n.LinkLocalIPs = toNetipAddrSlice(copts.linkLocalIPs.GetSlice())
	}
	return nil
}

func parseNetworkAttachmentOpt(ep opts.NetworkAttachmentOpts) (*network.EndpointSettings, error) {
	if strings.TrimSpace(ep.Target) == "" {
		return nil, errors.New("no name set for network")
	}
	if !container.NetworkMode(ep.Target).IsUserDefined() {
		if len(ep.Aliases) > 0 {
			return nil, errors.New("network-scoped aliases are only supported for user-defined networks")
		}
		if len(ep.Links) > 0 {
			return nil, errors.New("links are only supported for user-defined networks")
		}
	}

	epConfig := &network.EndpointSettings{
		GwPriority: ep.GwPriority,
	}
	epConfig.Aliases = append(epConfig.Aliases, ep.Aliases...)
	if len(ep.DriverOpts) > 0 {
		epConfig.DriverOpts = make(map[string]string)
		epConfig.DriverOpts = ep.DriverOpts
	}
	if len(ep.Links) > 0 {
		epConfig.Links = ep.Links
	}
	if ep.IPv4Address.IsValid() || ep.IPv6Address.IsValid() || len(ep.LinkLocalIPs) > 0 {
		epConfig.IPAMConfig = &network.EndpointIPAMConfig{
			IPv4Address:  ep.IPv4Address,
			IPv6Address:  ep.IPv6Address,

View on GitHub (pinned to 4f84911bfe)