docker/cli · error

unable to read public key from file

Error message

unable to read public key from file: %w

What it means

Returned by ingestPublicKeys() in `docker trust signer add` when os.OpenFile() fails to open a --key path read-only. %w wraps the OS error. This fires before any byte is read (the file could not be opened at all) — distinct from signer_add.go:128 which fires when OpenFile succeeds but ReadAll fails.

Solutions

  1. Verify the path exists and is a file: `ls -l <path>`.
  2. Use an absolute path to avoid working-directory ambiguity.
  3. Fix permissions/ownership so the current user can read it (e.g. `chmod u+r <path>`).
  4. Quote paths containing spaces.

Example fix

// before
$ docker trust signer add alice reg.io/app --key alice.pub
Error: unable to read public key from file: open alice.pub: no such file or directory

// after
$ docker trust signer add alice reg.io/app --key /home/alice/.docker/trust/keys/alice.pub
Defensive patterns

Strategy: validation

Validate before calling

// Verify the key file is openable before invoking signer add
func checkKeyFile(path string) error {
    f, err := os.Open(path)
    if err != nil { return fmt.Errorf("key file %s: %w", path, err) }
    f.Close()
    return nil
}

Type guard

func keyFileReadable(path string) bool {
    info, err := os.Stat(path)
    if err != nil || info.IsDir() { return false }
    return true
}

Prevention

When it happens

Trigger: Passing `--key <path>` where the path does not exist, is a directory, or the process lacks read permission. Also when the path has a typo or is relative to an unexpected working directory.

Common situations: Typo in the key path; key file on a different host/container not mounted; permissions too restrictive (0600 owned by another user); path with spaces unquoted.

Related errors


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

Appendix: source

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

		}
	}

	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 {
			return nil, fmt.Errorf("unable to read public key from file: %w", err)
		}

		// Parse PEM bytes into type PublicKey
		pubKey, err := tufutils.ParsePEMPublicKey(pubKeyBytes)
		if err != nil {
			return nil, fmt.Errorf("could not parse public key from file: %s: %w", pubKeyPath, err)
		}
		pubKeys = append(pubKeys, pubKey)
	}
	return pubKeys, nil
}

View on GitHub (pinned to 4f84911bfe)