docker/cli · error

service

Error message

service %s: %w

What it means

Wrap error produced in the Services() conversion loop when convertServiceSecrets fails for a service (service.go:42-44). It prefixes the inner error with the service name so you know which service's secret references were invalid. The inner error is typically 'undefined secret %q' (from service.go:255) or a failure from servicecli.ParseSecrets contacting the Swarm API.

Solutions

  1. Add the missing secret definition under the top-level `secrets:` key in the compose file.
  2. If the secret lives outside compose, declare it with `external: true` so compose does not try to create it.
  3. Check the inner error (unwrapped via errors.Is/As) to distinguish 'undefined secret' from a Swarm API failure and fix accordingly.

Example fix

// before
services:
  web:
    image: nginx
    secrets:
      - tls-cert
// after
services:
  web:
    image: nginx
    secrets:
      - tls-cert
secrets:
  tls-cert:
    file: ./certs/tls.pem
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-check that every secret a service uses is declared before calling convert.Services.
func validateSecrets(cfg *composetypes.Config) error {
    for _, svc := range cfg.Services {
        for _, s := range svc.Secrets {
            if _, ok := cfg.Secrets[s.Source]; !ok {
                return fmt.Errorf("service %s: undefined secret %q", svc.Name, s.Source)
            }
        }
    }
    return nil
}

Try / catch

result, err := convert.Services(ctx, namespace, cfg, apiClient)
if err != nil {
    var inner error
    if errors.As(err, &inner) {
        // log svcName + inner; surface a user-friendly message
    }
    return err
}

Prevention

When it happens

Trigger: Calling convert.Services(...) on a compose Config whose service references a secret not declared in the top-level secrets map, or when the Swarm API client cannot resolve a referenced secret by ID/name.

Common situations: A service lists `secrets: [mysecret]` but the top-level `secrets:` block omits it; renamed a secret in one place but not both; the secret object does not exist in Swarm and is not marked external; API connectivity issues during ParseSecrets.

Related errors


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

Appendix: source

Thrown at cli/compose/convert/service.go:44

const (
	defaultNetwork = "default"
	// LabelImage is the label used to store image name provided in the compose file
	LabelImage = "com.docker.stack.image"
)

// Services from compose-file types to engine API types
func Services(
	ctx context.Context,
	namespace Namespace,
	config *composetypes.Config,
	apiClient client.APIClient,
) (map[string]swarm.ServiceSpec, error) {
	result := make(map[string]swarm.ServiceSpec)
	for _, service := range config.Services {
		secrets, err := convertServiceSecrets(ctx, apiClient, namespace, service.Secrets, config.Secrets)
		if err != nil {
			return nil, fmt.Errorf("service %s: %w", service.Name, err)
		}
		configs, err := convertServiceConfigObjs(ctx, apiClient, namespace, service, config.Configs)
		if err != nil {
			return nil, fmt.Errorf("service %s: %w", service.Name, err)
		}

		serviceSpec, err := Service(namespace, service, config.Networks, config.Volumes, secrets, configs)
		if err != nil {
			return nil, fmt.Errorf("service %s: %w", service.Name, err)
		}
		result[service.Name] = serviceSpec
	}

	return result, nil
}

// Service converts a ServiceConfig into a swarm ServiceSpec
func Service(

View on GitHub (pinned to 4f84911bfe)