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

worm's-eye view photography of concrete building
worm's-eye view photography of concrete building

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