Fixing ‘ClusterIssuer Not Found’ in Terraform: CRD Plan-Time Validation Issue
This blog explains why Terraform fails at the plan phase when deploying Kubernetes CRDs like ClusterIssuer, even before resources are applied. It walks through the root cause of schema validation and shows practical fixes, including staged runs and using alternative providers.
3/28/20262 min read
A few days ago, during a production release, I hit a surprising roadblock—Terraform wouldn’t even let me reach the deployment step. The error showed up during the plan phase itself, preventing the deployment of the ClusterIssuer resource altogether!
Issue Encountered During Production Release
During a production release, the following error occurred:
Error: API did not recognize GroupVersionKind from manifest (CRD may not be installed)
with kubernetes_manifest.ClusterIssuer,
on main.tf line 64, in resource "kubernetes_manifest" "ClusterIssuer":
no matches for kind "ClusterIssuer" in group "cert-manager.io"
Background
kubernetes_manifest is a Terraform resource.
It allows any Kubernetes API object (including Custom Resource Definitions and Custom Resources) to be provisioned using Terraform.
It validates the schema definition (structure of the object) at plan time only.
Problem Explanation
Validation happens before the actual apply phase.
If the CRD is not already installed, Terraform cannot validate the Custom Resource.
This results in an error during the Terraform run.
Even adding depends_on does not resolve this issue.
Example Scenario
If the certmanager_issuer_crd CRD is not already installed, Terraform throws an error and prevents the creation of the Custom Resource.
resource "kubernetes_manifest" "certmanager_issuer_crd" {
manifest = {
apiVersion = "apiextensions.k8s.io/v1"
kind = "CustomResourceDefinition"
metadata = {
name = "issuers.cert-manager.io"
}
----
----
}
}
Deploying a ClusterIssuer requires the CRD to exist during terraform plan:
resource "kubernetes_manifest" "letsencrypt_issuer" {
manifest = {
apiVersion = "cert-manager.io/v1"
kind = "ClusterIssuer"
metadata = {
name = "letsencrypt-prod"
}
---
----
}
depends_on = [kubernetes_manifest.certmanager_issuer_crd]
}
Key Limitation
kubernetes_manifest performs schema validation at plan time.
The CRD must already exist before planning.
depends_on does not help because validation occurs before dependency resolution at apply time.
Solutions
1. Separate Terraform Runs
Execute Terraform in two stages:
First run: Install the CRD.
Second run: Create the Custom Resource.
If using kubernetes_manifest, this approach is required.
2. Use kubectl_manifest as an Alternative
Registering the provider is mandatory.
kubectl_manifest does not validate schema definitions.
It applies raw YAML, similar to running kubectl apply.
This avoids the plan-time validation limitation.
# Deploy cert-manager CustomResourceDefinition
resource "kubectl_manifest" "certmanager_crd" {
yaml_body = <<YAML
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: issuers.cert-manager.io
spec:
names:
kind: Issuer
plural: issuers
scope: Namespaced
group: cert-manager.io
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
YAML
depends_on = [kubectl_manifest.certmanager_namespace]
}
# Deploy cert-manager ClusterIssuer
resource "kubectl_manifest" "letsencrypt_issuer" {
yaml_body = <<YAML
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: admin@example.com
privateKeySecretRef:
name: letsencrypt-prod
solvers:
- http01:
ingress:
class: nginx
YAML
depends_on = [kubectl_manifest.certmanager_crd]
}
Summary
Ensure CRDs are installed before creating Custom Resources when using kubernetes_manifest.
Either:
Use separate Terraform runs, or
Switch to kubectl_manifest for more flexibility.
Small nuance
It is not only a problem with Terraform; it is a limitation of the Kubernetes provider in how it tries to validate resources early. That is useful for catching mistakes but creates conflicts with dynamically introduced resources like CRDs.
References
https://github.com/hashicorp/terraform-provider-kubernetes/issues/2597
https://oneuptime.com/blog/post/2026-02-23-crds-custom-resources-terraform/view
