Solving the 'No Matches for Kind' Error: Managing CRDs in Helm
Stop facing 'no matches for kind' errors in Helm. Learn how to use the crds/ directory and pre-install hooks to ensure Custom Resource Definitions are ready before your app deploys.
06 Mar 2026, 16:13 UTC

A common failure when deploying Kubernetes operators via Helm is the no matches for kind "MyResource" in version "v1" error. This happens because Helm attempts to create a Custom Resource (CR) before the Kubernetes API server has finished registering the Custom Resource Definition (CRD). The result is a failed release that requires manual cleanup of orphaned resources.
The most reliable way to solve this is to separate the CRD lifecycle from the application lifecycle using Helm's dedicated crds/ directory and explicit hooks to enforce ordering.
The Role of the crds/ Directory
Since Helm 3, charts can include a crds/ folder at the root. Any YAML files placed here are installed before any templates in the templates/ directory are rendered. This ensures the API server knows about the new resource type before the chart tries to instantiate it.
Crucially, Helm treats resources in the crds/ directory differently than those in templates/: they are not tracked as part of the release. This means they are not deleted when you run helm uninstall, preventing the accidental deletion of data if multiple releases share the same CRD.
Version Requirement: This behavior is standardized in Helm 3.8+. Verify your version by running this command in your terminal:
helm versionYou will need cluster-admin permissions or specific RBAC rights to create apiextensions.k8s.io/v1 CustomResourceDefinitions.
Enforcing Order with Pre-Install Hooks
While the crds/ directory handles initial installation, complex charts often benefit from a pre-install hook to guarantee the API server has fully processed the CRD before the rest of the deployment proceeds. A hook is a specialized resource that runs at a specific phase of the Helm lifecycle.
By adding a hook with a low hook-weight, you can create a synchronization point. Using the before-hook-creation delete policy ensures that the hook object itself doesn't clutter the namespace after the installation succeeds.
Worked Example: Operator Deployment
Consider a chart for a custom database operator. The goal is to ensure the Database CRD exists before the Database instance is created.
Chart Structure:
my-operator/
Chart.yaml
crds/
database.example.com.yaml
templates/
crd-sync-hook.yaml
operator-deployment.yaml
database-instance.yaml
The Hook Configuration (templates/crd-sync-hook.yaml):
apiVersion: v1
kind: ConfigMap
metadata:
name: crd-install-sync
annotations:
"helm.sh/hook": pre-install
"helm.sh/hook-weight": "-5"
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
data:
status: "waiting-for-crd"
To verify the installation sequence without modifying your cluster, run the following command from your local machine:
helm install my-release ./my-operator --dry-run --debugCheck the output to ensure the CRD from the
crds/folder is listed before theoperator-deploymentanddatabase-instance. If the sequence is correct, proceed with the actual installation:helm install my-release ./my-operator --namespace operator-system --create-namespaceTrade-offs and Limitations
The primary trade-off of using the
crds/directory is that Helm will not upgrade CRDs. If you update the schema incrds/database.example.com.yamland runhelm upgrade, Helm ignores the change because it does not track these files in the release history.This prevents accidental breaking changes to the API but means that schema migrations must be handled manually via
kubectl applyor a separate migration job. If you move CRDs into thetemplates/folder to gain upgrade capabilities, you risk the "no matches for kind" error during the initial install and the accidental deletion of all custom resources during uninstall.Practical Verification
After installation, verify that the CRD is registered and the custom resource is active using these commands:
# Check if the CRD is registered in the cluster kubectl get crd database.example.com # Check if the actual instance was created successfully kubectl get database -n operator-systemIf the second command returns the resource instead of an error, the ordering was successful.
Actionable Summary
- Place all CRD manifests in the
crds/root directory. - Use a
pre-installhook intemplates/with a negativehook-weightto enforce strict ordering. - Verify the sequence using
--dry-run --debugbefore deploying. - Perform CRD schema updates manually using
kubectl apply, as Helm will not automate these upgrades.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.