docker/cli · error

network is declared as external, but could not be found…

Error message

network %q is declared as external, but could not be found. You need to create a swarm-scoped network before the stack is deployed

What it means

Raised by validateExternalNetworks when a Compose network declared as external (external: true) does not exist on the Swarm. External networks must be pre-created by the operator because stack deploy will not create them; the check fails fast with a NotFound (errdefs.IsNotFound) from NetworkInspect.

Solutions

  1. Pre-create the network as swarm-scoped overlay: `docker network create -d overlay <name>`.
  2. Make the compose entry match the real network name (external networks use their actual name, no stack prefix).
  3. If the network should be managed by the stack, remove `external: true` so stack deploy creates it.
  4. Document required pre-existing networks in your deploy runbook/automation.

Example fix

# before (compose.yml)
networks:
  shared:
    external: true
# after
docker network create -d overlay shared
docker stack deploy -c compose.yml mystack
Defensive patterns

Strategy: validation

Validate before calling

// Pre-flight: ensure each external network exists and is swarm-scoped before deploy
func ensureExternalNetworks(ctx context.Context, c client.NetworkAPIClient, names []string) error {
    for _, name := range names {
        res, err := c.NetworkInspect(ctx, name, client.NetworkInspectOptions{})
        if errdefs.IsNotFound(err) {
            return fmt.Errorf("external network %q missing; create it with: docker network create -d overlay %s", name, name)
        } else if err != nil {
            return err
        } else if res.Network.Scope != "swarm" {
            return fmt.Errorf("external network %q is %q-scoped, needs swarm", name, res.Network.Scope)
        }
    }
    return nil
}

Try / catch

if err := stackDeploy(ctx, cli, opts, cfg); err != nil {
    if strings.Contains(err.Error(), "declared as external, but could not be found") {
        // prompt operator to pre-create, then retry
    }
    return err
}

Prevention

When it happens

Trigger: Deploying a stack whose compose file declares `networks: { foo: { external: true } }` while no network named 'foo' exists. The loop in deploy_composefile.go:90 calls NetworkInspect for each external network and, on NotFound, returns this error before any service is deployed.

Common situations: Forgetting to `docker network create -d overlay foo` before deploying; using a different name in compose than the actual network; deploying against a fresh Swarm that hasn't had shared networks provisioned; environment drift between staging and prod Swarms.

Related errors


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

Appendix: source

Thrown at cli/command/stack/deploy_composefile.go:100

		}
		for nw := range serviceConfig.Networks {
			serviceNetworks[nw] = struct{}{}
		}
	}
	return serviceNetworks
}

func validateExternalNetworks(ctx context.Context, apiClient client.NetworkAPIClient, externalNetworks []string) error {
	for _, networkName := range externalNetworks {
		if !container.NetworkMode(networkName).IsUserDefined() {
			// Networks that are not user defined always exist on all nodes as
			// local-scoped networks, so there's no need to inspect them.
			continue
		}
		res, err := apiClient.NetworkInspect(ctx, networkName, client.NetworkInspectOptions{})
		switch {
		case errdefs.IsNotFound(err):
			return fmt.Errorf("network %q is declared as external, but could not be found. You need to create a swarm-scoped network before the stack is deployed", networkName)
		case err != nil:
			return err
		case res.Network.Scope != "swarm":
			return fmt.Errorf("network %q is declared as external, but it is not in the right scope: %q instead of \"swarm\"", networkName, res.Network.Scope)
		}
	}
	return nil
}

func createSecrets(ctx context.Context, dockerCLI command.Cli, secrets []swarm.SecretSpec) error {
	apiClient := dockerCLI.Client()

	for _, secretSpec := range secrets {
		res, err := apiClient.SecretInspect(ctx, secretSpec.Name, client.SecretInspectOptions{})
		switch {
		case err == nil:
			// secret already exists, then we update that
			_, err := apiClient.SecretUpdate(ctx, res.Secret.ID, client.SecretUpdateOptions{

View on GitHub (pinned to 4f84911bfe)