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
- Declare the secret in the top-level `secrets:` block (from file, environment, or external).
- For secrets already in Swarm, use `external: true` with the real name.
- 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
- Always pair a service's `secrets:` entry with a top-level `secrets:` declaration.
- Mark Swarm-managed secrets external: true.
- Automate a config-lint step in CI.
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)