docker/cli · error · invalidParameterErr

unable to get system cert pool

Error message

unable to get system cert pool: %w

What it means

Returned by loadTLSConfig in docker-trust's registry package (registry.go:53) when x509.SystemCertPool() fails while initializing RootCAs for a .crt certificate being loaded. On Linux SystemCertPool reads the system bundle; failure usually means the bundle path is unreadable or the OS provides none. The error is wrapped via invalidParam.

Solutions

  1. Install the system CA bundle: `apt-get install ca-certificates` / `apk add ca-certificates`.
  2. Ensure the bundle at /etc/ssl/certs/ca-certificates.crt (or SSL_CERT_FILE) is readable and valid PEM.
  3. Pre-populate tlsConfig.RootCAs yourself before calling ReadCertsDirectory so the SystemCertPool path is skipped.
  4. Set SSL_CERT_FILE/SSL_CERT_DIR to a known-good bundle.

Example fix

// before
tlsConfig := &tls.Config{}
if err := registry.ReadCertsDirectory(tlsConfig, certDir); err != nil { ... }

// after: seed RootCAs so SystemCertPool() is never called
pool := x509.NewCertPool()
if b, err := os.ReadFile("/etc/ssl/certs/ca-certificates.crt"); err == nil {
    pool.AppendCertsFromPEM(b)
}
tlsConfig.RootCAs = pool
err := registry.ReadCertsDirectory(tlsConfig, certDir)
Defensive patterns

Strategy: validation

Validate before calling

// Seed RootCAs so SystemCertPool() is never called by ReadCertsDirectory
pool := x509.NewCertPool()
if b, err := os.ReadFile(os.Getenv("SSL_CERT_FILE")); err == nil {
    pool.AppendCertsFromPEM(b)
} else if b, err := os.ReadFile("/etc/ssl/certs/ca-certificates.crt"); err == nil {
    pool.AppendCertsFromPEM(b)
}
tlsConfig.RootCAs = pool

Try / catch

// Fallback: if SystemCertPool fails, build a fresh pool
if _, err := x509.SystemCertPool(); err != nil {
    tlsConfig.RootCAs = x509.NewCertPool()
}

Prevention

When it happens

Trigger: registry.ReadCertsDirectory encounters a *.crt file, RootCAs is nil, and x509.SystemCertPool() returns an error — e.g. on a minimal/container environment without /etc/ssl/certs/ca-certificates.crt, or where that file exists but cannot be parsed.

Common situations: Running docker-trust inside a stripped-down container or scratch image with no CA bundle, an Alpine image without ca-certificates installed, a read-only filesystem blocking the bundle, or a corrupted CA bundle.

Related errors


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

Appendix: source

Thrown at cmd/docker-trust/internal/registry/registry.go:53

func loadTLSConfig(ctx context.Context, directory string, tlsConfig *tls.Config) error {
	fs, err := os.ReadDir(directory)
	if err != nil {
		if os.IsNotExist(err) {
			return nil
		}
		return invalidParam(err)
	}

	for _, f := range fs {
		if ctx.Err() != nil {
			return ctx.Err()
		}
		switch filepath.Ext(f.Name()) {
		case ".crt":
			if tlsConfig.RootCAs == nil {
				systemPool, err := x509.SystemCertPool()
				if err != nil {
					return invalidParam(fmt.Errorf("unable to get system cert pool: %w", err))
				}
				tlsConfig.RootCAs = systemPool
			}
			fileName := filepath.Join(directory, f.Name())
			logrus.Debugf("crt: %s", fileName)
			data, err := os.ReadFile(fileName)
			if err != nil {
				return err
			}
			tlsConfig.RootCAs.AppendCertsFromPEM(data)
		case ".cert":
			certName := f.Name()
			keyName := certName[:len(certName)-5] + ".key"
			logrus.Debugf("cert: %s", filepath.Join(directory, certName))
			if !hasFile(fs, keyName) {
				return invalidParamf("missing key %s for client certificate %s. CA certificates must use the extension .crt", keyName, certName)
			}
			cert, err := tls.LoadX509KeyPair(filepath.Join(directory, certName), filepath.Join(directory, keyName))

View on GitHub (pinned to 4f84911bfe)