docker/cli · error

could not add signer to repo

Error message

could not add signer to repo: %s: %w

What it means

Returned by addSignerToRepo() in `docker trust signer add` when addStagedSigner() fails to create the targets/<signerName> delegation role with the supplied public key(s) and the targets/releases delegation. %s is the signer name (role prefix stripped), %w is the notary staging error. Mirrors the bootstrap error in sign.go:214 but occurs in the explicit signer-add path on an already-initialized (or just-initialized) repo.

Solutions

  1. Check whether the signer already exists with `docker trust inspect <repo>` and remove it first if you intend to replace its keys.
  2. Ensure the --key file is a valid PEM public key generated for notary (see ingestPublicKeys parsing).
  3. Clear stale staged changes: notary stages changes; a previously failed add may have left pending changes — re-run or use a fresh client.
  4. Read the wrapped %w for the notary delegation error and address it (conflict, invalid key, etc.).

Example fix

// before
$ docker trust signer add alice reg.io/app --key alice.pub
Error: could not add signer to repo: alice: ...

// after — remove existing signer then re-add with the new key
$ docker trust signer remove alice reg.io/app -f
$ docker trust signer add alice reg.io/app --key alice-new.pub
Defensive patterns

Strategy: validation

Validate before calling

// Before adding, check the signer role does not already exist (avoid conflict)
func signerExists(repo, name string) (bool, error) {
    out, err := exec.Command("docker", "trust", "inspect", repo).CombinedOutput()
    if err != nil { return false, err }
    return strings.Contains(string(out), "\""+name+"\""), nil
}

Try / catch

// If add fails on conflict, remove the existing signer then retry once
if out, err := exec.CommandContext(ctx, "docker", "trust", "signer", "add", name, repo, "--key", key).CombinedOutput(); err != nil {
    if strings.Contains(string(out), "already") || strings.Contains(string(out), "conflict") {
        _ = exec.CommandContext(ctx, "docker", "trust", "signer", "remove", name, repo, "-f").Run()
        // retry add
    }
}

Prevention

When it happens

Trigger: Running `docker trust signer add <name> <repo> --key <file>` where AddDelegationRoleAndKeys or AddDelegationPaths rejects the staging change — e.g. the delegation role already exists with a conflicting key set, or the staged metadata is malformed.

Common situations: Re-adding a signer whose role already exists; supplying a public key that does not match the expected PEM/type; partial trust state left over from a failed prior add.

Related errors


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

Appendix: source

Thrown at cmd/docker-trust/trust/signer_add.go:109

	}

	if _, err = notaryRepo.ListTargets(); err != nil {
		switch err.(type) {
		case client.ErrRepoNotInitialized, client.ErrRepositoryNotExist:
			_, _ = fmt.Fprintf(dockerCLI.Out(), "Initializing signed repository for %s...\n", repoName)
			if err := getOrGenerateRootKeyAndInitRepo(notaryRepo); err != nil {
				return trust.NotaryError(repoName, err)
			}
			_, _ = fmt.Fprintf(dockerCLI.Out(), "Successfully initialized %q\n", repoName)
		default:
			return trust.NotaryError(repoName, err)
		}
	}

	newSignerRoleName := data.RoleName(path.Join(data.CanonicalTargetsRole.String(), signerName))

	if err := addStagedSigner(notaryRepo, newSignerRoleName, signerPubKeys); err != nil {
		return fmt.Errorf("could not add signer to repo: %s: %w", strings.TrimPrefix(newSignerRoleName.String(), "targets/"), err)
	}

	return notaryRepo.Publish()
}

func ingestPublicKeys(pubKeyPaths []string) ([]data.PublicKey, error) {
	pubKeys := []data.PublicKey{}
	for _, pubKeyPath := range pubKeyPaths {
		// Read public key bytes from PEM file, limit to 1 KiB
		pubKeyFile, err := os.OpenFile(pubKeyPath, os.O_RDONLY, 0o666)
		if err != nil {
			return nil, fmt.Errorf("unable to read public key from file: %w", err)
		}
		defer pubKeyFile.Close()
		// limit to
		l := io.LimitReader(pubKeyFile, 1<<20)
		pubKeyBytes, err := io.ReadAll(l)
		if err != nil {

View on GitHub (pinned to 4f84911bfe)