docker/cli · error

undefined secret

Error message

undefined secret %q

What it means

Returned by the secret lookup closure inside convertServiceSecrets when a secret referenced by a service is not present in the top-level secretSpecs map (service.go:253-256). The named secret must be declared before it can be attached.

Solutions

  1. Declare the secret in the top-level `secrets:` block (from file, environment, or external).
  2. For secrets already in Swarm, use `external: true` with the real name.
  3. Correct the service's secret name to match an existing declaration.

Example fix

// before
services:
  app:
    image: myapp
    secrets: [db-password]
// after
services:
  app:
    image: myapp
    secrets: [db-password]
secrets:
  db-password:
    external: true
Defensive patterns

Strategy: validation

Validate before calling

func validateSecretsDeclared(cfg *composetypes.Config) error {
    for _, svc := range cfg.Services {
        for _, s := range svc.Secrets {
            if _, ok := cfg.Secrets[s.Source]; !ok {
                return fmt.Errorf("undefined secret %q", s.Source)
            }
        }
    }
    return nil
}

Prevention

When it happens

Trigger: A service's `secrets:` list names a secret that does not appear under the top-level `secrets:` key. The lookup at service.go:254 returns false and the error fires.

Common situations: Secret added to a service but not declared at the top level; secret renamed inconsistently; secret intended to be supplied externally but not marked `external: true`.

Related errors


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

Appendix: source

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

	sort.Slice(nets, func(i, j int) bool {
		return nets[i].Target < nets[j].Target
	})
	return nets, nil
}

// TODO: fix secrets API so that SecretAPIClient is not required here
func convertServiceSecrets(
	ctx context.Context,
	apiClient client.SecretAPIClient,
	namespace Namespace,
	secrets []composetypes.ServiceSecretConfig,
	secretSpecs map[string]composetypes.SecretConfig,
) ([]*swarm.SecretReference, error) {
	lookup := func(key string) (composetypes.FileObjectConfig, error) {
		secretSpec, exists := secretSpecs[key]
		if !exists {
			return composetypes.FileObjectConfig{}, fmt.Errorf("undefined secret %q", key)
		}
		return composetypes.FileObjectConfig(secretSpec), nil
	}

	refs := make([]*swarm.SecretReference, 0, len(secrets))
	for _, secret := range secrets {
		obj, err := convertFileObject(namespace, composetypes.FileReferenceConfig(secret), lookup)
		if err != nil {
			return nil, err
		}

		file := swarm.SecretReferenceFileTarget(obj.File)
		refs = append(refs, &swarm.SecretReference{
			File:       &file,
			SecretName: obj.Name,
		})
	}

View on GitHub (pinned to 4f84911bfe)