docker/cli · error

private key is encrypted - support for encrypted private…

Error message

private key is encrypted - support for encrypted private keys has been removed, see https://docs.docker.com/go/deprecated/

What it means

Returned by Endpoint.tlsConfig when x509.IsEncryptedPEMBlock reports the PEM private key is passphrase-protected (DEK-Info present). Docker removed support for encrypted private keys because they cannot be used unattended (no way to supply a passphrase to the daemon/client automatically), so an encrypted key is explicitly rejected.

Solutions

  1. Generate or convert an unencrypted PEM key: 'openssl rsa -in encrypted.key -out plain.key'.
  2. Recreate the docker context using the unencrypted key file.
  3. If a passphrase is mandatory by policy, store the context with an unencrypted key protected by filesystem permissions instead.

Example fix

# before
openssl genrsa -aes256 -out key.pem 4096
# after
openssl genrsa -out key.pem 4096
chmod 600 key.pem
Defensive patterns

Strategy: validation

Validate before calling

block, _ := pem.Decode(keyBytes)
if block != nil && x509.IsEncryptedPEMBlock(block) { //nolint:staticcheck
    return errors.New("encrypted keys are unsupported; decrypt the key first")
}

Type guard

func isUnencryptedPEMKey(b []byte) bool {
    block, _ := pem.Decode(b)
    return block != nil && !x509.IsEncryptedPEMBlock(block) //nolint:staticcheck
}

Prevention

When it happens

Trigger: Creating a docker context with a TLS key that was generated with a passphrase (e.g. 'openssl genrsa -aes256'). The key has PEM headers like '-----BEGIN ENCRYPTED PRIVATE KEY-----' or contains DEK-Info.

Common situations: Reusing an SSH or TLS key generated with a passphrase for Docker context TLS. A security policy mandates encrypted keys, conflicting with Docker's deprecation.

Related errors


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

Appendix: source

Thrown at cli/context/docker/load.go:67

	}
	var tlsOpts []func(*tls.Config)
	if ep.TLSData != nil && ep.TLSData.CA != nil {
		certPool := x509.NewCertPool()
		if !certPool.AppendCertsFromPEM(ep.TLSData.CA) {
			return nil, errors.New("failed to retrieve context tls info: ca.pem seems invalid")
		}
		tlsOpts = append(tlsOpts, func(cfg *tls.Config) {
			cfg.RootCAs = certPool
		})
	}
	if ep.TLSData != nil && ep.TLSData.Key != nil && ep.TLSData.Cert != nil {
		keyBytes := ep.TLSData.Key
		pemBlock, _ := pem.Decode(keyBytes)
		if pemBlock == nil {
			return nil, errors.New("no valid private key found")
		}
		if x509.IsEncryptedPEMBlock(pemBlock) { //nolint:staticcheck // SA1019: x509.IsEncryptedPEMBlock is deprecated, and insecure by design
			return nil, errors.New("private key is encrypted - support for encrypted private keys has been removed, see https://docs.docker.com/go/deprecated/")
		}

		x509cert, err := tls.X509KeyPair(ep.TLSData.Cert, keyBytes)
		if err != nil {
			return nil, fmt.Errorf("failed to retrieve context tls info: %w", err)
		}
		tlsOpts = append(tlsOpts, func(cfg *tls.Config) {
			cfg.Certificates = []tls.Certificate{x509cert}
		})
	}
	if ep.SkipTLSVerify {
		tlsOpts = append(tlsOpts, func(cfg *tls.Config) {
			cfg.InsecureSkipVerify = true
		})
	}
	return tlsconfig.ClientDefault(tlsOpts...), nil
}

View on GitHub (pinned to 4f84911bfe)