The intersection of Infrastructure as Code (IaC) and container orchestration has evolved beyond simple cluster provisioning. While cloud-specific providers like azurerm for Azure Kubernetes Service (AKS) or aws for Elastic Kubernetes Service (EKS) handle the underlying virtual machines and control plane, the internal configuration of the cluster—namespaces, pods, services, and Custom Resource Definitions (CRDs)—requires a more granular approach. The HashiCorp Kubernetes provider serves as the primary interface for this lifecycle management, allowing engineers to treat Kubernetes objects as first-class Terraform resources.
Integrating Kubernetes manifests into Terraform allows for a unified workflow where the cluster and the applications running upon it are versioned together. However, the transition from raw YAML manifests—the native language of Kubernetes—to HashiCorp Configuration Language (HCL) introduces specific technical challenges, particularly when dealing with Custom Resource Definitions (CRDs) and the timing of resource application.
Understanding the Terraform Kubernetes Provider Architecture
The Terraform Kubernetes provider is a specialized plugin maintained internally by HashiCorp. It functions as a translation layer, converting HCL declarations into API calls that the Kubernetes API server understands. By utilizing this provider, operators can achieve full lifecycle management (create, read, update, delete) of Kubernetes resources without relying on external shell scripts or manual kubectl apply commands.
The provider serves as an interface between the Terraform core and the Kubernetes cluster. When a user defines a resource in HCL, the provider determines the current state of that resource via the API server, compares it to the desired state defined in the code, and executes the necessary changes.
Provider Configuration and Authentication
To communicate with a Kubernetes cluster, the provider must be authenticated. The configuration varies depending on whether the cluster is local, managed by a cloud provider, or a custom deployment.
Local Cluster Configuration
For local development environments such as minikube, Terraform typically relies on the local kubeconfig file located at ~/.kube/config. This file contains the necessary cluster information and certificates required to establish a secure connection.
Explicit Provider Configuration
In professional production environments, relying on a local kubeconfig is often insufficient. It is common to define variables for the host, client certificate, client key, and cluster CA certificate. These values are often stored as base64-encoded strings in a secrets manager and decoded at runtime.
Below is a comprehensive example of a base configuration for the Kubernetes provider:
```hcl
terraform {
required_providers {
kubernetes = {
source = "hashicorp/kubernetes"
version = "~> 3.0"
}
}
}
variable "host" {
type = string
}
variable "client_certificate" {
type = string
}
variable "client_key" {
type = string
}
variable "clustercacertificate" {
type = string
}
provider "kubernetes" {
host = var.host
clientcertificate = base64decode(var.clientcertificate)
clientkey = base64decode(var.clientkey)
clustercacertificate = base64decode(var.clustercacertificate)
}
```
To obtain the values for these variables from a running cluster (such as one created via Kind), administrators can use the kubectl config view command:
bash
kubectl config view --minify --flatten --context=kind-terraform-learn
Managing Standard Resources vs. Custom Resource Definitions (CRDs)
One of the primary distinctions in Kubernetes management is the difference between built-in resources (like Pods and Deployments) and Custom Resources (CRs) governed by CRDs.
The Typed Resource Approach
The official hashicorp/kubernetes provider includes explicitly implemented resource types. These are "typed" resources, meaning the provider knows the exact schema of the object it is managing. This provides strong validation but requires the provider maintainers to update the code every time a new Kubernetes API feature is released.
The kubernetes_manifest Resource
To handle the flexibility of Custom Resources without waiting for provider updates, the kubernetes_manifest resource was introduced. This allows users to define resources that the provider does not have a built-in type for. However, this resource has a critical dependency: the CRD schema must already exist in the cluster at the time of the Terraform plan. If the CRD is being created in the same Terraform apply as the manifest, the plan phase may fail because the API server cannot yet validate the schema of the custom resource.
Transitioning from YAML to HCL
Kubernetes engineers often have a library of .yaml files that define their applications. To use these within the Terraform Kubernetes provider, they must be converted to HCL maps.
The Conversion Process
The most efficient way to convert a YAML manifest to HCL is by utilizing the yamldecode() function combined with the terraform console.
Consider a manifest for an OpenFaaS function named cows.yaml:
yaml
apiVersion: openfaas.com/v1
kind: Function
metadata:
name: showcow
namespace: openfaas-fn
spec:
name: showcow
handler: node show_cow.js
image: alexellis2/ascii-cows-openfaas:0.1
To convert this, run the following command:
bash
echo 'yamldecode(file("cows.yaml"))' | terraform console
The output will be a Terraform-compatible map:
hcl
{
"apiVersion" = "openfaas.com/v1"
"kind" = "Function"
"metadata" = {
"name" = "showcow"
"namespace" = "openfaas-fn"
}
"spec" = {
"handler" = "node show_cow.js"
"image" = "alexellis2/ascii-cows-openfaas:0.1"
"name" = "showcow"
}
}
Implementing the Manifest in Terraform
Once converted, the data is placed inside a kubernetes_manifest resource block.
hcl
resource "kubernetes_manifest" "openfaas_fn_showcow" {
manifest = {
apiVersion = "openfaas.com/v1"
kind = "Function"
metadata = {
name = "showcow"
namespace = "openfaas-fn"
}
spec = {
handler = "node show_cow.js"
image = "alexellis2/ascii-cows-openfaas:0.1"
name = "showcow"
}
}
}
The kubectl Provider: An Alternative for Raw YAML
While kubernetes_manifest is powerful, the requirement that schemas exist at plan time can create a "chicken-and-egg" problem during initial cluster bootstrapping. To solve this, the kubectl provider (created by Gavin Bunney) serves as a vital alternative.
Why Use the kubectl Provider?
The kubectl provider is designed to mimic the behavior of the kubectl apply command. Instead of translating resources into HCL maps that must be validated against a known schema during the planning phase, the kubectl provider accepts raw YAML.
The following table highlights the core differences between the official Kubernetes manifest resource and the kubectl provider.
| Feature | kubernetes_manifest (Official) |
kubectl Provider (Gavin Bunney) |
|---|---|---|
| Input Format | HCL Maps / yamldecode |
Raw YAML |
| Schema Validation | Required at Plan time | Applied at Apply time (Like kubectl apply) |
| CRD Handling | Requires CRD to exist before plan | Can apply CRDs and CRs in one go |
| Maintenance | Official HashiCorp Support | Community/Third-party |
| Use Case | Stable, known Kubernetes resources | Dynamic CRDs and raw manifest migration |
Practical Implementation Workflow
When deploying a complex application involving CRDs and manifests, a structured workflow is necessary to avoid dependency errors.
Step 1: Environment Setup
Create a dedicated directory for the project to maintain isolation of the state file.
bash
mkdir learn-terraform-deploy-nginx-kubernetes
cd learn-terraform-deploy-nginx-kubernetes
Step 2: Initialization
Define the provider requirements in a kubernetes.tf or provider.tf file. After defining the providers, the terraform init command must be executed to download the necessary plugins. If modules or backend configurations are changed later, the initialization command must be rerun to re-sync the working directory.
Step 3: Execution and State Tracking
When applying a manifest, Terraform tracks the resource in its state file. For a kubernetes_manifest resource, the plan output shows the mapping of the HCL manifest to the resulting Kubernetes object.
During a successful application of an OpenFaaS function, the logs will indicate:
+ resource "kubernetes_manifest" "openfaas_fn_showcow" will be created- The
manifestblock containing the API version, kind, and spec. - The
objectblock containing the final state, including fields that are "known after apply" (such as annotations, limits, and requests).
Technical Summary of Resource Interaction
The following table summarizes how Terraform interacts with different Kubernetes object types depending on the method chosen.
| Object Type | Recommended Method | Reason |
|---|---|---|
| Pods / Services / ConfigMaps | Typed Resources | Strong typing and official support. |
| Existing CRDs | kubernetes_manifest |
Allows HCL integration for known custom schemas. |
| New CRDs + Custom Resources | kubectl Provider |
Bypasses plan-time schema validation. |
| YAML-heavy migrations | kubectl Provider |
No need to convert thousands of lines to HCL. |
Conclusion
The orchestration of Kubernetes manifests via Terraform represents a significant leap in infrastructure maturity. By moving away from imperative shell scripts and toward a declarative model, organizations can ensure that their cluster state is reproducible and auditable. The official hashicorp/kubernetes provider is the gold standard for managing standard resources and established Custom Resources, providing the benefit of integrated state management and strong validation.
However, the technical limitation regarding CRD schema existence at plan time creates a gap that the kubectl provider effectively fills. For engineers tasked with bootstrapping entirely new environments where CRDs and their corresponding resources must be deployed simultaneously, the kubectl provider's ability to apply raw YAML mimics the native Kubernetes experience while maintaining the benefits of Terraform's dependency graph.
The optimal strategy for a modern DevOps pipeline is a hybrid approach: utilize typed resources for core infrastructure, kubernetes_manifest for stable application extensions, and the kubectl provider for the initial deployment of custom controllers and their required definitions. This layered approach maximizes the strengths of the Terraform ecosystem while minimizing the friction associated with the rigid nature of HCL when facing the highly dynamic API of Kubernetes.
Sources
- oneuptime.com/blog/post/2026-02-09-terraform-kubectl-provider-crd/view
- spacelift.io/blog/terraform-kubernetes-deployment
- developer.hashicorp.com/terraform/tutorials/kubernetes/kubernetes-crd-faas
- spacelift.io/blog/terraform-kubernetes-provider
- github.com/hashicorp/terraform-provider-kubernetes
- developer.hashicorp.com/terraform/tutorials/kubernetes/kubernetes-provider