docker/cli · error

file contents must be in PEM format

Error message

file contents must be in PEM format

What it means

Thrown by PEMFile.Set (cli/command/swarm/opts.go:148) for the `--ca-cert`/`--ca-key` flags during swarm CA rotation. The file at the given path is read and run through pem.Decode; if no PEM block is found (Decode returns nil), the file is not a valid PEM certificate/key and is rejected.

Solutions

  1. Provide a PEM-encoded file (begins with `-----BEGIN CERTIFICATE-----`).
  2. Convert DER to PEM: `openssl x509 -inform der -in cert.der -out cert.pem`.
  3. Verify before passing: `openssl x509 -in cert.pem -noout` (keys: `openssl pkey -in key.pem -noout`).

Example fix

// before
docker swarm ca --rotate --ca-cert cert.der

// after
openssl x509 -inform der -in cert.der -out cert.pem
docker swarm ca --rotate --ca-cert cert.pem
Defensive patterns

Strategy: validation

Validate before calling

// Validate PEM before passing to --ca-cert/--ca-key.
b, err := os.ReadFile(path)
if err != nil { return err }
if block, _ := pem.Decode(b); block == nil {
	return fmt.Errorf("%s is not PEM-encoded", path)
}

Type guard

func isPEM(b []byte) bool {
	block, _ := pem.Decode(b)
	return block != nil
}

Prevention

When it happens

Trigger: Passing `--ca-cert <path>` (or `--ca-key`) where the file is DER-encoded, plain text, empty, or otherwise not PEM, e.g. `docker swarm ca --rotate --ca-cert cert.der`.

Common situations: Wrong encoding (DER instead of PEM); pointing at the wrong file; a key file with no PEM headers; trailing content that breaks the parser; cert generated by a tool that outputs non-PEM by default.

Related errors


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

Appendix: source

Thrown at cli/command/swarm/opts.go:148

// Type returns the type of this option.
func (*PEMFile) Type() string {
	return "pem-file"
}

// String returns the path to the pem file
func (p *PEMFile) String() string {
	return p.path
}

// Set parses a root rotation option
func (p *PEMFile) Set(value string) error {
	contents, err := os.ReadFile(value)
	if err != nil {
		return err
	}
	if pemBlock, _ := pem.Decode(contents); pemBlock == nil {
		return errors.New("file contents must be in PEM format")
	}
	p.contents, p.path = string(contents), value
	return nil
}

// Contents returns the contents of the PEM file
func (p *PEMFile) Contents() string {
	return p.contents
}

// parseExternalCA parses an external CA specification from the command line,
// such as protocol=cfssl,url=https://example.com.
func parseExternalCA(caSpec string) (*swarm.ExternalCA, error) {
	csvReader := csv.NewReader(strings.NewReader(caSpec))
	fields, err := csvReader.Read()
	if err != nil {
		return nil, err
	}

View on GitHub (pinned to 4f84911bfe)