helm/helm · error

failed to install CRD %s: %w

Error message

failed to install CRD %s: %w

What it means

Thrown by Install.installCRDs (pkg/action/install.go:200) when cfg.KubeClient.Build fails to parse/decode the CRD file's bytes into resource.Info objects. Build is called with validation off, so this specifically means the YAML/JSON could not be decoded into Kubernetes objects (syntax errors, missing apiVersion/kind), as opposed to cluster-side validation. The CRD name and the decode error are wrapped.

Source

Thrown at pkg/action/install.go:200

	return i.registryClient
}

func (i *Install) installCRDs(crds []chart.CRD) error {
	// We do these one file at a time in the order they were read.
	totalItems := []*resource.Info{}
	for _, obj := range crds {
		if obj.File == nil {
			return fmt.Errorf("failed to install CRD %s: file is empty", obj.Name)
		}

		if obj.File.Data == nil {
			return fmt.Errorf("failed to install CRD %s: file data is empty", obj.Name)
		}

		// Read in the resources
		res, err := i.cfg.KubeClient.Build(bytes.NewBuffer(obj.File.Data), false)
		if err != nil {
			return fmt.Errorf("failed to install CRD %s: %w", obj.Name, err)
		}

		if len(res) == 0 {
			return fmt.Errorf("failed to install CRD %s: resources are empty", obj.Name)
		}

		// Send them to Kube
		if _, err := i.cfg.KubeClient.Create(
			res,
			kube.ClientCreateOptionServerSideApply(i.ServerSideApply, i.ForceConflicts)); err != nil {
			// If the error is CRD already exists, continue.
			if apierrors.IsAlreadyExists(err) {
				crdName := obj.Name
				i.cfg.Logger().Debug("CRD is already present. Skipping", "crd", crdName)
				continue
			}
			return fmt.Errorf("failed to install CRD %s: %w", obj.Name, err)
		}

View on GitHub (pinned to 2a29f1770b)

Solutions

  1. Locate the failing file: the error names obj.Name, matching the crds/ path in the chart
  2. Validate locally: extract the file and run `kubectl apply --dry-run=client -f crd.yaml` or a YAML linter
  3. Fix syntax/structure (indentation, quoting, apiVersion: apiextensions.k8s.io/v1, kind: CustomResourceDefinition)
  4. Add chart linting to CI (`helm lint`, kubeconform on crds/) to catch this before release

Example fix

# before: broken indentation inside crds/example.com_things.yaml
spec:
  group: example.com
   names: # one space too many

# after
spec:
  group: example.com
  names:
Defensive patterns

Strategy: validation

Validate before calling

// lint CRD files as part of chart CI
data, _ := os.ReadFile("crds/thing.yaml")
var doc map[string]any
if err := yaml.Unmarshal(data, &doc); err != nil {
    return fmt.Errorf("crds/thing.yaml does not parse: %w", err)
}
if doc["kind"] != "CustomResourceDefinition" { return errors.New("not a CRD") }

Type guard

func isParsableCRDFile(data []byte) bool {
    var doc map[string]any
    return yaml.Unmarshal(data, &doc) == nil && doc["apiVersion"] != nil && doc["kind"] != nil
}

Try / catch

if strings.Contains(err.Error(), "failed to install CRD") {
    // obj.Name in the message maps to the crds/ file; extract and run kubectl --dry-run=client on it
}

Prevention

When it happens

Trigger: A crds/*.yaml file that is invalid YAML (tabs, bad indentation, unterminated block), a document missing apiVersion/kind, or a file containing something other than Kubernetes manifests. Happens at install time before the CRD is sent to the cluster.

Common situations: Hand-written CRD files with indentation mistakes; CRDs copied from docs with markdown artifacts; multi-document files where an accidental separator leaves an empty or malformed document; CRDs converted from v1beta1 by scripts that broke the YAML structure.

Related errors


AI-assisted analysis of helm/helm@2a29f1770b (2026-08-15). Data as JSON: /api/errors/f77f838b9c7341b0. Report an issue: GitHub.