docker/cli · error
invalid restart policy: maximum retry count cannot be…
Error message
invalid restart policy: maximum retry count cannot be negative
What it means
Returned by convertRestartPolicy (service.go:497-499) when parsing the legacy 'restart' string (e.g. 'unless-stopped:3') and the parsed MaximumRetryCount is negative. The legacy format is 'name[:count]'; a negative count is invalid because retries cannot be negative. Note this path only runs for the legacy service.restart field - service.deploy.restart_policy is handled earlier (lines 479-486) and bypasses this check.
Solutions
- Set a non-negative retry count: restart: "on-failure:3".
- For unlimited retries use a high positive number or 'always'/'unless-stopped' (which ignore the count).
- Prefer the modern deploy.restart_policy block (max_attempts) over the legacy string where possible.
Example fix
# before
services:
web:
restart: "on-failure:-5"
# after
services:
web:
restart: "on-failure:5" Defensive patterns
Strategy: validation
Validate before calling
// Validate a legacy 'restart' string before handing it to conversion.
func validateLegacyRestart(restart string) error {
if restart == "" {
return nil
}
p, err := opts.ParseRestartPolicy(restart)
if err != nil {
return err
}
if p.MaximumRetryCount < 0 {
return errors.New("restart: maximum retry count cannot be negative")
}
return nil
} Type guard
// legacyRestartIsNonNegative reports whether the parsed retry count is valid.
func legacyRestartIsNonNegative(restart string) bool {
p, err := opts.ParseRestartPolicy(restart)
if err != nil {
return false
}
return p.MaximumRetryCount >= 0
} Prevention
- Prefer deploy.restart_policy.max_attempts over the legacy 'restart: name:count' string.
- If templating a retry count, clamp it to >= 0 before emitting the string.
- Run 'docker compose config' to validate before deploy.
When it happens
Trigger: In docker-compose.yml: restart: "on-failure:-5" or any 'name:<negative>' value. opts.ParseRestartPolicy (service.go:493) parses the count; if it is < 0 the guard at line 497 fires.
Common situations: Typo or sign error in the retry count. Templating that computes a count which can go negative under some inputs. Misunderstanding the legacy syntax and writing 'on-failure:-1' intending 'infinite'.
Related errors
- test and disable can't be set at the same time
- replicas can only be used with replicated or replicated-job…
- images options are incompatible with type volume
- tmpfs options are incompatible with type volume
- bind options are incompatible with type volume
AI-assisted analysis of docker/cli@4f84911bfe (2026-08-07).
Data as JSON: /api/errors/b6c478bf9f0c6d1c.
Report an issue: GitHub.
Appendix: source
Thrown at cli/compose/convert/service.go:498
// TODO: log or error if both "service.restart" and "service.deploy.restartpolicy" are set.
return &swarm.RestartPolicy{
Condition: swarm.RestartPolicyCondition(restartPolicy.Condition),
Delay: composetypes.ConvertDurationPtr(restartPolicy.Delay),
MaxAttempts: restartPolicy.MaxAttempts,
Window: composetypes.ConvertDurationPtr(restartPolicy.Window),
}, nil
}
if restart == "" {
return nil, nil
}
// Fall back to the legacy service.restart restart-policy.
policy, err := opts.ParseRestartPolicy(restart)
if err != nil {
return nil, err
}
if policy.MaximumRetryCount < 0 {
return nil, errors.New("invalid restart policy: maximum retry count cannot be negative")
}
uint64Ptr := func(i int) *uint64 {
if i <= 0 {
return nil
}
p := uint64(i)
return &p
}
switch policy.Name {
case container.RestartPolicyDisabled, "":
return nil, nil
case container.RestartPolicyAlways, container.RestartPolicyUnlessStopped:
return &swarm.RestartPolicy{
Condition: swarm.RestartPolicyConditionAny,
MaxAttempts: uint64Ptr(policy.MaximumRetryCount),
}, nil
case container.RestartPolicyOnFailure:View on GitHub (pinned to 4f84911bfe)