{"record":{"id":"8757afe109cab046","repo":"docker/cli","slug":"service-is-already-attached-to-network-s","errorCode":null,"errorMessage":"service is already attached to network %s","messagePattern":"service is already attached to network (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cli/command/service/update.go","lineNumber":1343,"sourceCode":"\tfor _, nw := range specNetworks {\n\t\tif _, exists := idsToRemove[nw.Target]; exists {\n\t\t\tcontinue\n\t\t}\n\n\t\tnewNetworks = append(newNetworks, nw)\n\t\texistingNetworks[nw.Target] = struct{}{}\n\t}\n\n\tif flags.Changed(flagNetworkAdd) {\n\t\tvalues := flags.Lookup(flagNetworkAdd).Value.(*opts.NetworkOpt)\n\t\tnetworks := convertNetworks(*values)\n\t\tfor _, nw := range networks {\n\t\t\tnwID, err := resolveNetworkID(ctx, apiClient, nw.Target)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif _, exists := existingNetworks[nwID]; exists {\n\t\t\t\treturn fmt.Errorf(\"service is already attached to network %s\", nw.Target)\n\t\t\t}\n\t\t\tnw.Target = nwID\n\t\t\tnewNetworks = append(newNetworks, nw)\n\t\t\texistingNetworks[nw.Target] = struct{}{}\n\t\t}\n\t}\n\n\tsort.Slice(newNetworks, func(i, j int) bool {\n\t\treturn newNetworks[i].Target < newNetworks[j].Target\n\t})\n\n\tspec.TaskTemplate.Networks = newNetworks\n\treturn nil\n}\n\n// updateCredSpecConfig updates the value of the credential spec Config field\n// to the config ID if the credential spec has changed. it mutates the passed\n// spec. it does not handle the case where the credential spec specifies a","sourceCodeStart":1325,"sourceCodeEnd":1361,"githubUrl":"https://github.com/docker/cli/blob/4f84911bfe8811e9b028e4b1fee8e7510be79387/cli/command/service/update.go#L1325-L1361","documentation":"Raised by docker service update when --network-add targets a network the service is already attached to. The updateNetworks function resolves the network name to an ID and checks it against existingNetworks; a duplicate would create a redundant attachment, so the CLI rejects it rather than silently no-op.","triggerScenarios":"Running `docker service update --network-add <name-or-id> <service>` where <name-or-id> resolves (via resolveNetworkID) to the same ID as a network already present in spec.TaskTemplate.Networks. The check is at update.go:1342 inside the flagNetworkAdd branch.","commonSituations":"Running the same --network-add command twice (e.g. from an idempotent-but-not-reconciled deploy script); adding a network by an alias/driver-specific name that resolves to an already-attached ID; copy-pasting a network-add line during troubleshooting.","solutions":["Verify the service's current networks with `docker service inspect <service>` (look at Spec.TaskTemplate.Networks) and skip networks already attached.","Use a different network name/id, or omit the --network-add for the duplicate.","Make your automation idempotent: diff desired vs. existing networks before invoking update.","If the intent is to change attachment options (aliases), remove and re-add with --network-rm then --network-add."],"exampleFix":"// before\ndocker service update --network-add mynet myservice   # myservice already on mynet\n// after\ndocker service inspect myservice --format '{{json .Spec.TaskTemplate.Networks}}'\n# then only add networks not already present","handlingStrategy":"validation","validationCode":"// Before calling docker service update --network-add, check current attachments\n// Pseudocode using the daemon API:\nfunc desiredNetworksToAdd(existing []string, desired []string) []string {\n    have := map[string]struct{}{}\n    for _, n := range existing {\n        have[n] = struct{}{}\n    }\n    var add []string\n    for _, d := range desired {\n        if _, ok := have[d]; !ok {\n            add = append(add, d)\n        }\n    }\n    return add // pass these (and only these) to --network-add\n}","typeGuard":null,"tryCatchPattern":"// Treat the duplicate as a no-op success in automation\nerr := svcUpdate(ctx, addNetworks)\nif err != nil && strings.Contains(err.Error(), \"already attached to network\") {\n    // idempotent: the desired state is already satisfied\n    return nil\n}","preventionTips":["Diff desired vs. existing networks from service inspect before issuing update.","Treat 'already attached' as an idempotent success in wrappers.","Avoid re-running raw add commands; reconcile against current state."],"tags":["docker","service-update","network","swarm","duplicate"],"backgroundTag":null,"analyzedSha":"4f84911bfe8811e9b028e4b1fee8e7510be79387","analyzedAt":"2026-08-07T12:15:29.814Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}