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
- Locate the failing file: the error names obj.Name, matching the crds/ path in the chart
- Validate locally: extract the file and run `kubectl apply --dry-run=client -f crd.yaml` or a YAML linter
- Fix syntax/structure (indentation, quoting, apiVersion: apiextensions.k8s.io/v1, kind: CustomResourceDefinition)
- 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
- Run kubeconform/yamllint over crds/ in chart CI
- Copy CRDs from generated sources (controller-gen) instead of hand-editing
- Prefer apiextensions.k8s.io/v1 with structural schemas on all supported clusters
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
- cannot load Chart.yaml: %w
- cannot load values.yaml: %w
- unable to build kubernetes object for %s hook %s: %w
- failed to install CRD %s: resources are empty
- no API version specified
AI-assisted analysis of helm/helm@2a29f1770b (2026-08-15).
Data as JSON: /api/errors/f77f838b9c7341b0.
Report an issue: GitHub.