<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0"><channel><title>Pulumi Blog: Pulumi Content Team</title><link>https://www.pulumi.com/blog/authors/pulumi-content-team/</link><description>Pulumi blog posts: Pulumi Content Team.</description><language>en-us</language><pubDate>Tue, 25 Aug 2026 02:08:47 +0000</pubDate><item><title>Best Kubernetes Infrastructure as Code Tools in 2026</title><link>https://www.pulumi.com/blog/best-kubernetes-iac-tools-2026/</link><pubDate>Fri, 14 Aug 2026 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/best-kubernetes-iac-tools-2026/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/best-kubernetes-iac-tools-2026/index.png" /&gt;
&lt;p&gt;There is no single best Kubernetes infrastructure as code tool, because &amp;ldquo;Kubernetes IaC&amp;rdquo; actually spans three different jobs. For provisioning the cluster and its cloud dependencies, Pulumi and Terraform (or OpenTofu) are the strongest general-purpose options. For templating and packaging workloads, Helm and Kustomize dominate. For continuous reconciliation once things are running, Argo CD and Flux lead the GitOps category. The right stack usually combines one tool from each layer, not a single tool that claims to do all three.&lt;/p&gt;
&lt;h2 id="what-counts-as-infrastructure-as-code-for-kubernetes"&gt;What counts as infrastructure as code for Kubernetes?&lt;/h2&gt;
&lt;p&gt;Kubernetes infrastructure as code work splits into three layers that get conflated constantly, and the confusion is where most tool comparisons go wrong.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;cluster and cloud layer&lt;/strong&gt; provisions the things Kubernetes itself sits on top of: the managed control plane (EKS, GKE, AKS), node pools, the VPC and subnets, IAM roles, load balancers, and cluster add-ons. Terraform, Pulumi, and cloud-native tools like CloudFormation operate here.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;in-cluster workload layer&lt;/strong&gt; defines what runs on the cluster once it exists: Deployments, Services, ConfigMaps, CustomResourceDefinitions, and the Helm charts or Kustomize overlays that template them. This is where Helm, Kustomize, and Crossplane&amp;rsquo;s custom resources live.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;delivery and reconciliation layer&lt;/strong&gt; keeps what&amp;rsquo;s declared in Git in sync with what&amp;rsquo;s actually running on the cluster, continuously, rather than as a one-shot apply. Argo CD and Flux own this layer, and they consume the output of the other two rather than replacing them.&lt;/p&gt;
&lt;p&gt;Most real Kubernetes platforms use tools from at least two of these layers together. A team might provision EKS with Terraform, package its application with Helm, and let Argo CD reconcile it continuously. Knowing which layer a tool actually addresses, rather than treating &amp;ldquo;Kubernetes IaC&amp;rdquo; as one shopping list, is the first decision that matters.&lt;/p&gt;
&lt;h2 id="pulumi-provisions-the-cluster-and-the-workloads-on-it-in-the-same-language"&gt;Pulumi provisions the cluster and the workloads on it in the same language&lt;/h2&gt;
&lt;p&gt;Pulumi is a general-purpose infrastructure as code platform that supports Python, TypeScript, Go, C#, Java, and YAML, backed by a &lt;a href="https://www.pulumi.com/registry/packages/kubernetes/"&gt;dedicated Kubernetes provider&lt;/a&gt; that covers both the cluster layer and the workload layer in the same program. A single Pulumi stack can create an EKS cluster, its VPC and node groups, and the Deployments and Services that run inside it, with full dependency tracking between the two: Pulumi knows a Deployment depends on a cluster that does not exist yet, and sequences accordingly.&lt;/p&gt;
&lt;p&gt;For workloads, Pulumi offers two distinct ways to work with Helm charts. &lt;a href="https://www.pulumi.com/registry/packages/kubernetes/how-to-guides/choosing-the-right-helm-resource-for-your-use-case/"&gt;&lt;code&gt;kubernetes.helm.v4.Chart&lt;/code&gt;&lt;/a&gt; renders a chart client-side via &lt;code&gt;helm template&lt;/code&gt; into individual resources, giving full Pulumi state, diffing, and policy visibility into every object a chart creates, at the cost of not producing a real Helm release. &lt;code&gt;kubernetes.helm.sh/v3.Release&lt;/code&gt; installs through the embedded Helm SDK instead, producing an actual release with hooks, and can even import releases installed by the Helm CLI. Teams pick based on whether they need release semantics or resource-level visibility; Pulumi&amp;rsquo;s documentation is upfront that these are genuinely different tradeoffs, not that one supersedes the other. Kustomize overlays are supported directly through &lt;a href="https://www.pulumi.com/registry/packages/kubernetes/api-docs/kustomize/v2/directory/"&gt;&lt;code&gt;kubernetes.kustomize.v2.Directory&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As of the v4 provider, Server-Side Apply is the default for managing resources, which lets Pulumi share ownership of an object with other controllers safely instead of overwriting whatever they changed. Await and readiness logic (waiting for a Deployment&amp;rsquo;s rollout to finish before considering it &amp;ldquo;done&amp;rdquo;) is handled natively for common resource types, with &lt;code&gt;skipAwait&lt;/code&gt; and &lt;code&gt;waitFor&lt;/code&gt; escape hatches for anything unusual. As Pulumi engineer Bryce Lampe put it when the improved await logic shipped, &amp;ldquo;One of the advantages of using Pulumi to manage Kubernetes resources is that it natively and intuitively handles this problem of readiness and dependencies,&amp;rdquo; which matters more than it sounds: a naive apply-and-move-on model is a common source of flaky CI in Kubernetes pipelines.&lt;/p&gt;
&lt;p&gt;Pulumi also plugs into an existing GitOps workflow rather than requiring you to replace it: the &lt;a href="https://www.pulumi.com/registry/packages/kubernetes/api-docs/provider/"&gt;Kubernetes provider&amp;rsquo;s &lt;code&gt;renderYamlToDirectory&lt;/code&gt; option&lt;/a&gt; renders manifests to disk instead of applying them directly, a pattern Pulumi&amp;rsquo;s own provider documentation calls out as an Argo CD Config Management Plugin use case. The provider currently labels this a developer-preview beta feature, disabled by default, so treat it as a bridge worth piloting rather than a settled default for a GitOps handoff. For CRDs, &lt;a href="https://www.pulumi.com/docs/integrations/clouds/kubernetes/crd2pulumi/"&gt;&lt;code&gt;crd2pulumi&lt;/code&gt;&lt;/a&gt; generates strongly typed SDKs from CRD definitions, so a custom resource gets the same autocomplete and type-checking as a built-in one. Teams running Pulumi as a standing service inside the cluster can use the &lt;a href="https://www.pulumi.com/docs/integrations/clouds/kubernetes/pulumi-kubernetes-operator/"&gt;Pulumi Kubernetes Operator&lt;/a&gt; to drive stack updates from in-cluster custom resources.&lt;/p&gt;
&lt;p&gt;The honest tradeoff: Pulumi is a commercial platform with an open-source core, and a team fully committed to a YAML-only, kubectl-native workflow will find a general-purpose language layer to be more machinery than it needs for a handful of static manifests. Materialize, which runs multi-region Amazon EKS clusters, &lt;a href="https://www.pulumi.com/case-studies/materialize/#executive-summary"&gt;adopted Pulumi from day one to manage that complexity in Python&lt;/a&gt;, after finding that tools like Terraform required developers to learn a proprietary configuration language and created unnecessary friction; new engineers reach their first meaningful contribution in a week rather than the month the team estimates it would otherwise take. A team with one cluster and no cross-region variation may not have that problem to solve.&lt;/p&gt;
&lt;h2 id="terraform-and-opentofu-remain-the-default-for-the-cluster-layer-with-real-friction-at-the-workload-layer"&gt;Terraform and OpenTofu remain the default for the cluster layer, with real friction at the workload layer&lt;/h2&gt;
&lt;p&gt;Terraform (and its community-governed fork, OpenTofu, since HashiCorp&amp;rsquo;s Business Source License change) is still the most widely deployed way to provision the cluster itself: the AWS, Google, and Azure providers are mature, and the Kubernetes and Helm providers (currently at 3.2.1 and 3.2.0 respectively) let the same HCL codebase reach into the cluster afterward. That reach is also where the friction shows up. Terraform plans at plan time, before it has ever talked to a live cluster, so it frequently cannot know what a CRD&amp;rsquo;s schema actually looks like until it&amp;rsquo;s already been applied; this is a well-documented source of &amp;ldquo;plan differs from apply&amp;rdquo; surprises when CRDs and their consumers live in the same configuration. The common workaround is a two-stage apply, provisioning the cluster in one Terraform run and the workloads that depend on its CRDs in a second, which works but adds operational ceremony that a single-language, single-run tool doesn&amp;rsquo;t need. Our &lt;a href="https://www.pulumi.com/blog/terraform-kubernetes/"&gt;practical guide to Terraform and Kubernetes&lt;/a&gt; walks through where that friction shows up day to day and how a general-purpose language changes the testing and CRD story.&lt;/p&gt;
&lt;p&gt;HCL itself is also a genuine limit at the workload layer: templating a Deployment&amp;rsquo;s environment variables across ten similar services means either heavy use of &lt;code&gt;for_each&lt;/code&gt; and dynamic blocks, or accepting a lot of copy-pasted HCL, because HCL was designed as a configuration language rather than a general-purpose one with functions and reusable abstractions. None of this makes Terraform a poor choice for the cluster layer, where it remains a defensible default with a huge ecosystem of examples and modules. It&amp;rsquo;s a reason many Terraform shops hand workload templating off to Helm rather than fighting the Kubernetes provider for it.&lt;/p&gt;
&lt;h2 id="helm-is-the-default-packaging-format-and-now-defaults-to-safer-applies"&gt;Helm is the default packaging format, and now defaults to safer applies&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://helm.sh/"&gt;Helm&lt;/a&gt; is the de facto package manager for Kubernetes: charts bundle a set of manifests with a templating layer and values files, and the public Artifact Hub ecosystem aggregates charts from vendors and the community, so most common software (databases, ingress controllers, observability stacks) has a chart available, official or otherwise. Helm 4 made Server-Side Apply the default instead of the old three-way merge against a stored last-applied-configuration annotation, which reduces a class of drift bugs that plagued Helm 3 installs sharing objects with other controllers. It also added WASM-based plugins and an experimental v3 chart format.&lt;/p&gt;
&lt;p&gt;Helm&amp;rsquo;s real weakness is templating itself: charts are YAML run through Go&amp;rsquo;s text/template engine, which has no real type system, awkward conditionals, and error messages that point at rendered output rather than the source template. Values files also tend to sprawl as a chart tries to expose every possible customization, and a chart author&amp;rsquo;s defaults are a starting point you inherit rather than infrastructure you designed. Helm is excellent for consuming someone else&amp;rsquo;s well-maintained chart and mediocre as an authoring experience for your own complex, multi-service application, which is exactly why tools like Pulumi and Kustomize exist to sit on either side of it.&lt;/p&gt;
&lt;h2 id="kustomize-is-the-built-in-choice-for-environment-specific-overlays"&gt;Kustomize is the built-in choice for environment-specific overlays&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://kubernetes.io/docs/tasks/manage-kubernetes-objects/kustomization/"&gt;Kustomize&lt;/a&gt; ships inside &lt;code&gt;kubectl&lt;/code&gt; and takes a patch-based approach instead of a templating one: you write a base set of plain YAML manifests, then layer environment-specific overlays (a different replica count for staging, a different image tag for production) that patch the base without touching it. That model avoids Helm&amp;rsquo;s templating-language problem entirely, at the cost of being far less expressive for anything beyond structural changes to existing YAML: generating a resource conditionally, or computing a value, is awkward or impossible.&lt;/p&gt;
&lt;p&gt;The practical friction is version lag: the Kustomize binary bundled inside a given &lt;code&gt;kubectl&lt;/code&gt; release trails the standalone Kustomize CLI&amp;rsquo;s releases, so a team relying on kubectl&amp;rsquo;s bundled version can be several minor versions behind what&amp;rsquo;s documented upstream. Teams that hit this usually install the standalone &lt;code&gt;kustomize&lt;/code&gt; binary directly rather than depend on kubectl&amp;rsquo;s copy.&lt;/p&gt;
&lt;h2 id="crossplane-turns-cloud-resources-into-kubernetes-native-apis"&gt;Crossplane turns cloud resources into Kubernetes-native APIs&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.crossplane.io/"&gt;Crossplane&lt;/a&gt;, which graduated to a CNCF top-level project on November 6, 2025, takes a fundamentally different approach: instead of a CLI that applies configuration, cloud resources become &lt;a href="https://docs.crossplane.io/latest/managed-resources/managed-resources/"&gt;managed resources&lt;/a&gt;, native Kubernetes custom resources that a control-plane cluster reconciles continuously, overriding drift back to the desired state, the same way Kubernetes reconciles a Deployment. This is a genuinely different mental model from Terraform, Pulumi, or Helm, and it&amp;rsquo;s the right one for platform teams building a self-service layer where application teams request infrastructure (an S3 bucket, a Postgres instance) through the same &lt;code&gt;kubectl apply&lt;/code&gt; and RBAC model they already use for everything else.&lt;/p&gt;
&lt;p&gt;Crossplane v2 removed native patch-and-transform composition and introduced namespaced Composite Resources (XRs) alongside an alpha &amp;ldquo;Operations&amp;rdquo; feature for one-off imperative tasks. Claims, the v1 abstraction that let application teams request infrastructure without touching the (cluster-scoped) XR directly, were already namespaced; what changes in v2 is that they &lt;a href="https://docs.crossplane.io/v2.0/whats-new/"&gt;aren&amp;rsquo;t supported&lt;/a&gt; by the new namespaced or cluster-scoped XRs at all, and survive only when an XRD opts into the &lt;code&gt;LegacyCluster&lt;/code&gt; scope for backward compatibility. Teams on Crossplane v1 migrating to v2 should plan to move application teams onto namespaced XRs directly rather than assume claims carry forward as-is. It also means Crossplane&amp;rsquo;s learning curve is steeper than a CLI-based tool: composing custom APIs out of managed resources requires understanding Crossplane&amp;rsquo;s own compositional model in addition to the cloud provider&amp;rsquo;s resource shapes. Teams that don&amp;rsquo;t need a self-service internal platform, and just want to provision infrastructure for their own team, usually find Terraform or Pulumi a faster path to the same cloud resources.&lt;/p&gt;
&lt;h2 id="argo-cd-and-flux-reconcile-whats-running-and-assume-something-else-already-authored-it"&gt;Argo CD and Flux reconcile what&amp;rsquo;s running, and assume something else already authored it&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://argo-cd.readthedocs.io/"&gt;Argo CD&lt;/a&gt; (CNCF graduated December 2022, now at 3.5.1) and &lt;a href="https://fluxcd.io/"&gt;Flux&lt;/a&gt; (CNCF graduated November 2022, now at 2.9.4) are the two dominant GitOps controllers: both watch a Git repository and continuously compare the cluster&amp;rsquo;s actual state against what&amp;rsquo;s declared there, surfacing drift as it appears and correcting it automatically when self-healing is enabled, rather than waiting for the next scheduled apply. This is the delivery layer from the three-layer breakdown above, and it&amp;rsquo;s genuinely complementary to, not competitive with, Terraform, Pulumi, Helm, and Kustomize: Argo CD and Flux consume rendered manifests or chart references, they don&amp;rsquo;t generate cloud infrastructure or author application configuration themselves.&lt;/p&gt;
&lt;p&gt;The CNCF&amp;rsquo;s 2025 annual survey found that GitOps adoption tracks organizational maturity closely: 58% of self-identified &amp;ldquo;cloud native innovators&amp;rdquo; use GitOps extensively, compared to 23% of &amp;ldquo;adopters&amp;rdquo; earlier in their journey, which suggests GitOps is a practice teams grow into rather than a day-one requirement. Neither Argo CD nor Flux replaces the need for a provisioning tool at the cluster layer; a common and reasonable pattern is Terraform or Pulumi for the cluster and cloud dependencies, then Argo CD or Flux reconciling the workloads that run on it, with Pulumi&amp;rsquo;s &lt;code&gt;renderYamlToDirectory&lt;/code&gt; option (currently a developer-preview beta feature) designed to feed that second stage.&lt;/p&gt;
&lt;h2 id="cdk8s-and-kro-bring-code-and-native-apis-to-workload-definitions"&gt;cdk8s and kro bring code and native APIs to workload definitions&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/cdk8s-team/cdk8s"&gt;cdk8s&lt;/a&gt; applies the AWS CDK model to Kubernetes manifests: you define Deployments, Services, and other objects in TypeScript, Python, Java, or Go, and cdk8s synthesizes plain YAML at build time, which plays nicely with existing GitOps pipelines since the output is still just manifests. It&amp;rsquo;s a narrower tool than Pulumi in scope, generating Kubernetes YAML only rather than also provisioning the cluster and cloud resources around it, and it has no equivalent to Pulumi&amp;rsquo;s state-tracked diffing or drift detection since it only emits manifests for something else to apply. The core &lt;code&gt;cdk8s&lt;/code&gt; library is stable on the 2.x line; the higher-level &lt;code&gt;cdk8s+&lt;/code&gt; construct libraries are versioned and released separately per supported Kubernetes version.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/kubernetes-sigs/kro"&gt;kro&lt;/a&gt; takes Crossplane&amp;rsquo;s &amp;ldquo;infrastructure as a native API&amp;rdquo; idea and generalizes it: it &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/kro.html"&gt;composes any native Kubernetes resource as well as any CRD installed in the cluster&lt;/a&gt;, Crossplane&amp;rsquo;s managed resources included, into a new custom API that platform teams expose to their users. kro recently moved into the &lt;code&gt;kubernetes-sigs&lt;/code&gt; organization as a SIG Cloud Provider subproject, which is a credibility signal, but it remains genuinely alpha (&lt;code&gt;v1alpha1&lt;/code&gt; APIs, releases still in the 0.7 to 0.9.3 range) and is not yet a CNCF top-level project. It&amp;rsquo;s worth watching and piloting, not yet a safe default for anything you can&amp;rsquo;t easily rebuild.&lt;/p&gt;
&lt;p&gt;A handful of other projects are worth knowing about without needing a full section: &lt;a href="https://timoni.sh/"&gt;Timoni&lt;/a&gt; applies CUE&amp;rsquo;s type system to Kubernetes packaging as a Helm alternative; &lt;a href="https://kubevela.io/"&gt;KubeVela&lt;/a&gt; builds an application-delivery abstraction on top of the Open Application Model; &lt;a href="https://tanka.dev/"&gt;Tanka&lt;/a&gt; uses Jsonnet for templated manifests in the Grafana Labs ecosystem; and Ansible&amp;rsquo;s Kubernetes modules remain common in shops that already standardized on Ansible for configuration management generally.&lt;/p&gt;
&lt;h2 id="how-the-tools-compare-across-cluster-workload-and-delivery-layers"&gt;How the tools compare across cluster, workload, and delivery layers&lt;/h2&gt;
&lt;p&gt;Reconciliation model is the dimension most roundups leave out, and it matters as much as authoring language: a one-shot apply tool and a continuously reconciling controller solve different problems even when they touch the same YAML.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Authoring language&lt;/th&gt;
&lt;th&gt;Provisions cloud + cluster?&lt;/th&gt;
&lt;th&gt;Reconciliation model&lt;/th&gt;
&lt;th&gt;Maturity / governance&lt;/th&gt;
&lt;th&gt;Best fit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Pulumi&lt;/td&gt;
&lt;td&gt;Python, TypeScript, Go, C#, Java, YAML&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;One-shot apply (&lt;code&gt;pulumi up&lt;/code&gt;), continuous via Kubernetes Operator&lt;/td&gt;
&lt;td&gt;Open-source core, commercial platform&lt;/td&gt;
&lt;td&gt;Teams wanting one codebase for cluster + workloads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Terraform / OpenTofu&lt;/td&gt;
&lt;td&gt;HCL&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;One-shot apply&lt;/td&gt;
&lt;td&gt;Terraform: HashiCorp/IBM; OpenTofu: Linux Foundation&lt;/td&gt;
&lt;td&gt;Cluster and cloud provisioning, HCL-standardized shops&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Helm&lt;/td&gt;
&lt;td&gt;YAML + Go templates&lt;/td&gt;
&lt;td&gt;No (workload only)&lt;/td&gt;
&lt;td&gt;One-shot install/upgrade&lt;/td&gt;
&lt;td&gt;CNCF graduated&lt;/td&gt;
&lt;td&gt;Packaging and consuming shared application charts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kustomize&lt;/td&gt;
&lt;td&gt;YAML (patch-based)&lt;/td&gt;
&lt;td&gt;No (workload only)&lt;/td&gt;
&lt;td&gt;One-shot apply&lt;/td&gt;
&lt;td&gt;Built into &lt;code&gt;kubectl&lt;/code&gt;, part of Kubernetes SIG-CLI&lt;/td&gt;
&lt;td&gt;Environment overlays on plain manifests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Crossplane&lt;/td&gt;
&lt;td&gt;Kubernetes YAML / CRDs&lt;/td&gt;
&lt;td&gt;Yes (via managed resources)&lt;/td&gt;
&lt;td&gt;Continuous reconciliation&lt;/td&gt;
&lt;td&gt;CNCF graduated Nov 2025&lt;/td&gt;
&lt;td&gt;Self-service internal platforms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Argo CD&lt;/td&gt;
&lt;td&gt;Kubernetes YAML (consumed)&lt;/td&gt;
&lt;td&gt;No (delivery only)&lt;/td&gt;
&lt;td&gt;Continuous reconciliation&lt;/td&gt;
&lt;td&gt;CNCF graduated&lt;/td&gt;
&lt;td&gt;GitOps delivery, UI-driven ops&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flux&lt;/td&gt;
&lt;td&gt;Kubernetes YAML (consumed)&lt;/td&gt;
&lt;td&gt;No (delivery only)&lt;/td&gt;
&lt;td&gt;Continuous reconciliation&lt;/td&gt;
&lt;td&gt;CNCF graduated&lt;/td&gt;
&lt;td&gt;GitOps delivery, GitOps Toolkit composability&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cdk8s&lt;/td&gt;
&lt;td&gt;TypeScript, Python, Java, Go&lt;/td&gt;
&lt;td&gt;No (workload only)&lt;/td&gt;
&lt;td&gt;One-shot synth to YAML&lt;/td&gt;
&lt;td&gt;Stable 2.x core, CNCF Sandbox&lt;/td&gt;
&lt;td&gt;Code-first manifest generation feeding existing GitOps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;kro&lt;/td&gt;
&lt;td&gt;Kubernetes YAML / CRDs&lt;/td&gt;
&lt;td&gt;Yes (composes existing CRDs)&lt;/td&gt;
&lt;td&gt;Continuous reconciliation&lt;/td&gt;
&lt;td&gt;Alpha, &lt;code&gt;kubernetes-sigs&lt;/code&gt; subproject&lt;/td&gt;
&lt;td&gt;Composing custom platform APIs, early adopters&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="the-same-task-in-terraform-and-pulumi"&gt;The same task in Terraform and Pulumi&lt;/h2&gt;
&lt;p&gt;The clearest way to see the practical difference is the same job in both: create a managed EKS cluster and deploy a single workload onto it.&lt;/p&gt;
&lt;p&gt;Terraform provisions the cluster with the AWS provider, then reaches into it with the Kubernetes provider in the same configuration:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-hcl" data-lang="hcl"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;aws_eks_cluster&amp;#34; &amp;#34;main&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;app-cluster&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; role_arn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;aws_iam_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;eks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;arn&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;vpc_config&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; subnet_ids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;*&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="k"&gt;id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;kubernetes&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;aws_eks_cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;endpoint&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; cluster_ca_certificate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;base64decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;aws_eks_cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;certificate_authority&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;aws_eks_cluster_auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;token&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;kubernetes_deployment&amp;#34; &amp;#34;app&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;metadata&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;app&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;spec&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; replicas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;selector&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; match_labels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt; { app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;app&amp;#34;&lt;/span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;template&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;metadata&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; labels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt; { app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;app&amp;#34;&lt;/span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;spec&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;container&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;app&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;myorg/app:1.4.0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Pulumi, in TypeScript, does the same two-stage job with the dependency between them tracked automatically, rather than requiring a separate provider block wired up by hand:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="kr"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;@pulumi/aws&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="kr"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;eks&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;@pulumi/eks&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="kr"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;k8s&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;@pulumi/kubernetes&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;eks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;app-cluster&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;instanceType&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;t3.medium&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;desiredCapacity&lt;/span&gt;: &lt;span class="kt"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;k8s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Provider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;k8s-provider&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;kubeconfig&lt;/span&gt;: &lt;span class="kt"&gt;cluster.kubeconfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;k8s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;v1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Deployment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;app&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;spec&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;replicas&lt;/span&gt;: &lt;span class="kt"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;selector&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;matchLabels&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;app&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;labels&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;app&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;spec&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;containers&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;app&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;myorg/app:1.4.0&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Both are legitimate. Terraform&amp;rsquo;s version separates concerns across a provider block and requires knowing the Kubernetes provider&amp;rsquo;s HCL shape for a Deployment resource; Pulumi&amp;rsquo;s version is fewer moving pieces because the cluster&amp;rsquo;s kubeconfig flows directly into the next resource as a normal object reference, and the whole thing can be unit tested with the same test framework used for the application code it deploys.&lt;/p&gt;
&lt;h2 id="how-to-choose-a-kubernetes-iac-tool"&gt;How to choose a Kubernetes IaC tool&lt;/h2&gt;
&lt;p&gt;Most teams end up combining tools across the three layers rather than picking one. Working through these five steps in order avoids the most common mistake, which is picking a workload-layer tool (like Helm) and then trying to stretch it to also provision the cluster.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Decide who provisions the cluster and its cloud dependencies (VPC, node pools, IAM), and pick a cluster-layer tool for that job: Pulumi or Terraform/OpenTofu for most teams, or a cloud-native tool if you&amp;rsquo;re single-cloud and want the tightest console integration.&lt;/li&gt;
&lt;li&gt;Decide how workloads get packaged. If you&amp;rsquo;re consuming other people&amp;rsquo;s software (databases, ingress controllers, observability agents), Helm&amp;rsquo;s chart ecosystem is hard to beat. If you&amp;rsquo;re authoring your own application manifests, Kustomize overlays or a code-first approach (Pulumi, cdk8s) usually scale better than hand-templated Helm charts.&lt;/li&gt;
&lt;li&gt;Decide whether you need a self-service internal platform. If application teams should be able to request infrastructure through &lt;code&gt;kubectl apply&lt;/code&gt; without touching Terraform or Pulumi directly, Crossplane or kro are worth the steeper learning curve. If your platform team is the only one touching infrastructure, you likely don&amp;rsquo;t need this layer yet.&lt;/li&gt;
&lt;li&gt;Decide whether you need continuous reconciliation or a one-shot apply is enough. A small team deploying a few times a week can live with &lt;code&gt;terraform apply&lt;/code&gt; or &lt;code&gt;pulumi up&lt;/code&gt; in CI. A team running many clusters, or one where configuration drift is a recurring incident cause, benefits from Argo CD or Flux catching and correcting drift automatically.&lt;/li&gt;
&lt;li&gt;Match the result to your team&amp;rsquo;s actual skills, not the tool with the most GitHub stars. A team of application engineers who already write Python or TypeScript daily will move faster in a tool that speaks their language than in a new DSL, and a platform team that already lives in YAML and kubectl will get more value from Kustomize and Argo CD than from introducing a new language to learn.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="is-terraform-or-pulumi-better-for-kubernetes"&gt;Is Terraform or Pulumi better for Kubernetes?&lt;/h2&gt;
&lt;p&gt;Neither is universally better; the choice depends on what happens after the cluster exists. Terraform&amp;rsquo;s ecosystem of provider modules and examples is larger for pure cluster provisioning, and HCL is a smaller thing to learn than a full programming language. Pulumi&amp;rsquo;s advantage shows up once workloads enter the picture: because the cluster and its workloads live in the same general-purpose language, dependencies between them are tracked automatically, the same testing and CI tools used for application code apply to infrastructure code, and CRD-heavy workloads avoid the plan-time unknowns that come from HCL not having talked to a live cluster yet. Teams already standardized on HCL across their infrastructure will find less reason to switch; teams that want one codebase and one language for cluster and workloads together tend to prefer Pulumi.&lt;/p&gt;
&lt;h2 id="what-are-the-alternatives-to-helm"&gt;What are the alternatives to Helm?&lt;/h2&gt;
&lt;p&gt;Kustomize is the most common alternative for teams that find Helm&amp;rsquo;s templating language awkward, trading some expressiveness for patch-based simplicity on plain YAML. cdk8s replaces Helm&amp;rsquo;s templating with a real programming language that synthesizes YAML at build time. Pulumi can render Helm charts directly through kubernetes.helm.v4.Chart for full state visibility, or install them as genuine Helm releases through kubernetes.helm.sh/v3.Release, without requiring a separate templating tool at all. Timoni applies CUE&amp;rsquo;s type system as a more strictly typed alternative to Helm&amp;rsquo;s Go templates. None of these fully replace Helm&amp;rsquo;s chart ecosystem for consuming third-party software; they&amp;rsquo;re strongest for authoring your own application&amp;rsquo;s manifests.&lt;/p&gt;
&lt;h2 id="how-do-you-manage-a-managed-kubernetes-service-like-eks-gke-or-aks-as-code"&gt;How do you manage a managed Kubernetes service like EKS, GKE, or AKS as code?&lt;/h2&gt;
&lt;p&gt;Managed Kubernetes services expose the control plane as a cloud resource like any other, so the tools that provision it are the same cluster-layer tools used for the rest of your cloud footprint: Terraform, OpenTofu, or Pulumi, using each cloud&amp;rsquo;s native provider (aws_eks_cluster, google_container_cluster, azurerm_kubernetes_cluster, or Pulumi&amp;rsquo;s equivalent resources and the higher-level @pulumi/eks package). The node pools, VPC, subnets, and IAM roles the control plane depends on get provisioned in the same run. What happens after the cluster exists, deploying and reconciling workloads, is a separate decision covered by the workload and delivery layers above, not something the cluster-provisioning tool needs to also own.&lt;/p&gt;
&lt;h2 id="is-gitops-a-replacement-for-infrastructure-as-code"&gt;Is GitOps a replacement for infrastructure as code?&lt;/h2&gt;
&lt;p&gt;No. GitOps tools like Argo CD and Flux reconcile what&amp;rsquo;s already declared in Git against what&amp;rsquo;s running on the cluster; they don&amp;rsquo;t generate that declaration or provision the cloud infrastructure underneath it. A GitOps workflow still needs something upstream producing the manifests it reconciles, whether that&amp;rsquo;s Helm charts, Kustomize overlays, cdk8s output, or Pulumi&amp;rsquo;s renderYamlToDirectory rendering a stack to disk. GitOps is a delivery and drift-correction practice layered on top of infrastructure as code, not a substitute for the provisioning step itself.&lt;/p&gt;
&lt;h2 id="do-you-need-kubernetes-specific-iac-tooling-at-all"&gt;Do you need Kubernetes-specific IaC tooling at all?&lt;/h2&gt;
&lt;p&gt;Not always. A team running a single small cluster with a handful of stable workloads can reasonably manage everything with plain kubectl apply against version-controlled YAML and skip templating tools entirely; the complexity Helm, Kustomize, and Crossplane solve only shows up once you have multiple environments, multiple similar services, or multiple teams needing self-service access. The honest signal that it&amp;rsquo;s time to adopt tooling is repetition: the same YAML copied and hand-edited across environments, or the same cluster-provisioning steps run manually more than once, is worth automating before it causes an incident.&lt;/p&gt;
&lt;h2 id="which-kubernetes-iac-tool-should-you-choose"&gt;Which Kubernetes IaC tool should you choose?&lt;/h2&gt;
&lt;p&gt;There&amp;rsquo;s no single winner because &amp;ldquo;Kubernetes IaC&amp;rdquo; spans three different jobs. Most production setups combine a cluster-layer tool (Pulumi or Terraform), a workload-layer tool (Helm, Kustomize, or a code-first option), and often a delivery-layer tool (Argo CD or Flux) once the team is large enough to need continuous reconciliation. The five-step framework above is a better starting point than any single ranked list, because the right combination depends on your team&amp;rsquo;s language preferences, how many environments you run, and whether you need to expose infrastructure to other teams as a self-service API.&lt;/p&gt;
&lt;h2 id="frequently-asked-questions"&gt;Frequently asked questions&lt;/h2&gt;
&lt;h3 id="does-pulumi-replace-helm-and-kustomize"&gt;Does Pulumi replace Helm and Kustomize?&lt;/h3&gt;
&lt;p&gt;Not entirely. Pulumi can render Helm charts and apply Kustomize overlays directly from its Kubernetes provider, so a team can consume the existing Helm and Kustomize ecosystems from inside a Pulumi program rather than needing a separate CLI step. For authoring new manifests from scratch, Pulumi&amp;rsquo;s general-purpose language is an alternative to writing Helm templates or Kustomize bases, but plenty of teams use Pulumi for the cluster layer while still authoring workload manifests as Helm charts.&lt;/p&gt;
&lt;h3 id="can-you-use-more-than-one-of-these-tools-together"&gt;Can you use more than one of these tools together?&lt;/h3&gt;
&lt;p&gt;Yes, and most real deployments do. A common combination is a cluster-layer tool (Terraform or Pulumi) for the control plane and cloud dependencies, a workload-layer tool (Helm or Kustomize) for packaging what runs on it, and a delivery-layer tool (Argo CD or Flux) for continuous reconciliation. The three-layer breakdown at the top of this post exists precisely because these tools are usually complementary rather than competing for the same job.&lt;/p&gt;
&lt;h3 id="is-crossplane-a-replacement-for-terraform-or-pulumi"&gt;Is Crossplane a replacement for Terraform or Pulumi?&lt;/h3&gt;
&lt;p&gt;Not for most teams. Crossplane&amp;rsquo;s strength is exposing infrastructure as native Kubernetes APIs so application teams can self-serve through kubectl, which is a platform-engineering pattern, not a general substitute for a cluster-provisioning tool. A platform team still typically uses Terraform or Pulumi to provision the Crossplane control-plane cluster itself and configure Crossplane&amp;rsquo;s provider credentials.&lt;/p&gt;
&lt;h3 id="why-did-the-cncfs-2025-survey-findings-matter-for-this-comparison"&gt;Why did the CNCF&amp;rsquo;s 2025 survey findings matter for this comparison?&lt;/h3&gt;
&lt;p&gt;The CNCF&amp;rsquo;s 2025 Annual Cloud Native Survey found 82% of container users now run Kubernetes in production, up from 66% in 2023, and 47% cited cultural change, more than training or security, as the top adoption challenge. Those numbers matter for tool choice because Kubernetes adoption has moved well past early adopters into mainstream production use, which means the tooling decision is increasingly about fitting an existing team&amp;rsquo;s skills and processes rather than picking a tool for a greenfield experiment.&lt;/p&gt;
&lt;h2 id="where-to-go-next"&gt;Where to go next&lt;/h2&gt;
&lt;p&gt;For the cluster-provisioning layer specifically, the &lt;a href="https://www.pulumi.com/docs/iac/get-started/kubernetes/"&gt;Pulumi Kubernetes getting-started guide&lt;/a&gt; walks through creating a cluster and deploying a first workload. The &lt;a href="https://www.pulumi.com/docs/iac/comparisons/helm/"&gt;Pulumi vs. Helm comparison&lt;/a&gt; and &lt;a href="https://www.pulumi.com/docs/iac/comparisons/crossplane/"&gt;Pulumi vs. Crossplane comparison&lt;/a&gt; go deeper on the workload- and platform-layer tradeoffs summarized here, and the &lt;a href="https://www.pulumi.com/docs/iac/comparisons/terraform/"&gt;Pulumi vs. Terraform comparison&lt;/a&gt; covers the cluster-layer decision in more depth than a roundup can. If your infrastructure spans more than Kubernetes, &lt;a href="https://www.pulumi.com/blog/infrastructure-as-code-tools/"&gt;our broader infrastructure as code tools roundup&lt;/a&gt; compares the cloud-provisioning landscape beyond the cluster.&lt;/p&gt;
&lt;p&gt;For running AI workloads specifically on Kubernetes, &lt;a href="https://www.pulumi.com/blog/ai-agents-on-kubernetes/"&gt;our companion piece on provisioning and governing infrastructure for AI agents&lt;/a&gt; covers GPU-aware scheduling, credentials, and where policy as code and Pulumi&amp;rsquo;s Neo agent fit into that picture. And if a self-service platform for other teams is the eventual goal, &lt;a href="https://www.pulumi.com/docs/idp/"&gt;Pulumi&amp;rsquo;s IDP documentation&lt;/a&gt; covers the components, templates, and policy guardrails that turn infrastructure code into something other teams can safely consume without needing to understand Pulumi themselves.&lt;/p&gt;
&lt;p&gt;Whatever combination you land on, the layer breakdown here is the durable part: know whether you&amp;rsquo;re solving a cluster problem, a workload problem, or a reconciliation problem before you compare tools, because most of the disagreement in &amp;ldquo;best Kubernetes IaC tool&amp;rdquo; debates comes from comparing tools that were never solving the same problem in the first place.&lt;/p&gt;</description><author>Pulumi Content Team</author><category>kubernetes</category><category>infrastructure-as-code</category><category>platform-engineering</category><category>devops</category><category>helm</category></item><item><title>Terraform and Kubernetes: A Practical Guide for 2026</title><link>https://www.pulumi.com/blog/terraform-kubernetes/</link><pubDate>Fri, 07 Aug 2026 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/terraform-kubernetes/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/terraform-kubernetes/index.png" /&gt;
&lt;p&gt;Yes, Terraform can manage Kubernetes: the official &lt;code&gt;hashicorp/kubernetes&lt;/code&gt; provider lets you declare Deployments, Services, and other objects as HCL resources, and community providers like &lt;code&gt;kubectl&lt;/code&gt; fill in the gaps. It works well for many teams. The friction shows up around two well-documented limits — provider ordering and plan-time API access — and around testing, where a general-purpose language changes what&amp;rsquo;s possible.&lt;/p&gt;
&lt;p&gt;That friction matters more in 2026 than it did a few years ago. Kubernetes infrastructure now sits next to AI-driven engineering workflows: agents that propose changes, run previews, and open pull requests need infrastructure code they can read, test, and reason about with the same tools they use for application code. A cluster definition written in HCL and a workload definition written in YAML are both harder for an agent — and a person — to unit test, refactor, or type-check than the equivalent in TypeScript, Python, or Go.&lt;/p&gt;
&lt;p&gt;This guide is about the &lt;em&gt;operating model&lt;/em&gt; for Kubernetes infrastructure: how the cluster, the platform layer, and the workloads on top of it get provisioned, tested, and shipped. It&amp;rsquo;s a different question from &amp;ldquo;should I write my Kubernetes manifests in YAML, HCL, or a real language,&amp;rdquo; which we cover in detail in &lt;a href="https://www.pulumi.com/blog/yaml-terraform-pulumi-whats-the-smart-choice-for-deployment-automation-with-kubernetes/"&gt;YAML, Terraform, or Pulumi: what&amp;rsquo;s the smart choice for deployment automation with Kubernetes?&lt;/a&gt; Read that post first if you&amp;rsquo;re deciding how to author manifests; read this one for the wider workflow — provisioning, testing, policy, and where AI agents fit.&lt;/p&gt;
&lt;h2 id="how-does-terraform-manage-kubernetes-today"&gt;How does Terraform manage Kubernetes today?&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;hashicorp/kubernetes&lt;/code&gt; provider (current release v3.2.1, requiring Terraform 1.0.0 or later) is the primary path, and teams typically combine it with one or two others depending on what they&amp;rsquo;re deploying:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;What it&amp;rsquo;s for&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Typed resources (&lt;code&gt;kubernetes_deployment_v1&lt;/code&gt;, &lt;code&gt;kubernetes_service_v1&lt;/code&gt;, etc.)&lt;/td&gt;
&lt;td&gt;Core, well-known object types&lt;/td&gt;
&lt;td&gt;Full HCL validation and typed attributes for the objects the provider models explicitly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kubernetes_manifest&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Custom resources or object types the provider doesn&amp;rsquo;t model yet&lt;/td&gt;
&lt;td&gt;Requires live API access during &lt;code&gt;terraform plan&lt;/code&gt;, which shapes how it can be sequenced (&lt;a href="#what-are-the-hard-parts-of-managing-kubernetes-with-terraform"&gt;details&lt;/a&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hashicorp/helm&lt;/code&gt; provider&lt;/td&gt;
&lt;td&gt;Installing Helm charts&lt;/td&gt;
&lt;td&gt;Wraps the Helm SDK; chart internals aren&amp;rsquo;t individually visible to Terraform&amp;rsquo;s plan/diff&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Community &lt;code&gt;kubectl&lt;/code&gt; provider (&lt;code&gt;alekc/kubectl&lt;/code&gt;, a maintained fork of &lt;code&gt;gavinbunney/kubectl&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Applying free-form YAML manifests&lt;/td&gt;
&lt;td&gt;Common workaround for &lt;code&gt;kubernetes_manifest&lt;/code&gt;&amp;rsquo;s plan-time requirement; newer versions support Terraform 1.10+ ephemeral resources so secrets don&amp;rsquo;t have to land in state&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="what-are-the-hard-parts-of-managing-kubernetes-with-terraform"&gt;What are the hard parts of managing Kubernetes with Terraform?&lt;/h2&gt;
&lt;p&gt;Two limitations show up often enough in practice that HashiCorp documents them directly, and they&amp;rsquo;re worth quoting rather than paraphrasing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Plan-time API access.&lt;/strong&gt; From the &lt;code&gt;kubernetes_manifest&lt;/code&gt; resource docs: &amp;ldquo;This resource requires API access during planning time. This means the cluster has to be accessible at plan time and thus cannot be created in the same apply operation. We recommend only using this resource for custom resources or resources not yet fully supported by the provider.&amp;rdquo; In practice this means you can&amp;rsquo;t create a cluster and populate it with &lt;code&gt;kubernetes_manifest&lt;/code&gt; resources in a single &lt;code&gt;terraform apply&lt;/code&gt; — the cluster has to exist and be reachable before Terraform can even plan the manifest resources.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Provider-credential ordering.&lt;/strong&gt; From the provider&amp;rsquo;s own index documentation: &amp;ldquo;When using interpolation to pass credentials to the Kubernetes provider from other resources, these resources SHOULD NOT be created in the same Terraform module where Kubernetes provider resources are also used. This will lead to intermittent and unpredictable errors which are hard to debug and diagnose. The root issue lies with the order in which Terraform itself evaluates the provider blocks vs. actual resources.&amp;rdquo; HashiCorp&amp;rsquo;s prescribed fix, also verbatim: &amp;ldquo;The most reliable way to configure the Kubernetes provider is to ensure that the cluster itself and the Kubernetes provider resources can be managed with separate &lt;code&gt;apply&lt;/code&gt; operations. Data-sources can be used to convey values between the two stages as needed.&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both are solvable — split cluster provisioning and workload deployment into separate applies (or separate Terraform workspaces/modules), and pass values between them with data sources or remote state. It&amp;rsquo;s a real pattern, and plenty of teams run it in production. It does mean two-stage pipelines and extra state plumbing wherever a cluster and its workloads are managed together.&lt;/p&gt;
&lt;p&gt;At a glance, here&amp;rsquo;s how Terraform&amp;rsquo;s Kubernetes provider and Pulumi differ on the points that matter most for cluster-plus-workload management:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Terraform (&lt;code&gt;hashicorp/kubernetes&lt;/code&gt;)&lt;/th&gt;
&lt;th&gt;Pulumi&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cluster and workloads in one run&lt;/td&gt;
&lt;td&gt;Requires a two-stage apply or separate modules&lt;/td&gt;
&lt;td&gt;Single program, single &lt;code&gt;pulumi up&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom resources / CRDs&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kubernetes_manifest&lt;/code&gt; needs live API access at plan time&lt;/td&gt;
&lt;td&gt;Typed CRD support generated from cluster schema, no plan-time cluster requirement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Provider-credential ordering&lt;/td&gt;
&lt;td&gt;Documented pitfall; HashiCorp recommends separate applies&lt;/td&gt;
&lt;td&gt;Ordinary language-level dependency, resolved by the runtime&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Testing&lt;/td&gt;
&lt;td&gt;Native &lt;code&gt;.tftest.hcl&lt;/code&gt; tests with &lt;code&gt;mock_provider&lt;/code&gt; (GA since Terraform 1.7), in a separate test language&lt;/td&gt;
&lt;td&gt;Standard test frameworks (pytest, Jest, Go testing, etc.) in the same suite as application code, plus &lt;code&gt;pulumi preview&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;HCL only (community &lt;code&gt;kubectl&lt;/code&gt; provider as a workaround for free-form YAML)&lt;/td&gt;
&lt;td&gt;Python, TypeScript, Go, C#, Java, or YAML/HCL&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="what-changes-when-kubernetes-infrastructure-is-written-in-a-general-purpose-language"&gt;What changes when Kubernetes infrastructure is written in a general-purpose language?&lt;/h2&gt;
&lt;p&gt;Pulumi&amp;rsquo;s Kubernetes provider is generated directly from the Kubernetes OpenAPI specifications (current version v4.33.0), so it stays current with the Kubernetes API surface automatically, and it runs inside a single Pulumi program in TypeScript, Python, Go, C#, Java, or YAML. Because the cluster resource and the workloads that depend on it are ordinary values in that program, sequencing them doesn&amp;rsquo;t require splitting into separate applies — Pulumi&amp;rsquo;s dependency graph resolves the ordering, and Server-Side Apply (the provider&amp;rsquo;s default since v4.0) handles upserts consistently across resource types.&lt;/p&gt;
&lt;p&gt;The provider also has built-in await logic for common object types — it knows a Deployment isn&amp;rsquo;t &amp;ldquo;done&amp;rdquo; until its replicas are available, a Service isn&amp;rsquo;t ready until it has endpoints or a load balancer ingress, and a Pod isn&amp;rsquo;t ready until its readiness probe passes. That behavior is tunable with annotations: &lt;code&gt;pulumi.com/skipAwait&lt;/code&gt;, &lt;code&gt;pulumi.com/timeoutSeconds&lt;/code&gt;, the experimental &lt;code&gt;pulumi.com/waitFor&lt;/code&gt; (supports a JSONPath value check, a JSONPath existence check, &lt;code&gt;condition=Synced&lt;/code&gt;, or a JSON array of conditions), and &lt;code&gt;pulumi.com/deletionPropagationPolicy&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For Helm, there are three options with different tradeoffs:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Resource&lt;/th&gt;
&lt;th&gt;How it works&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;helm.sh/v4.Chart&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Renders the chart client-side and manages each object as a distinct Pulumi resource; implemented as a component, so it works the same way across every Pulumi language, including Java and YAML; supports OCI registries&lt;/td&gt;
&lt;td&gt;Teams that want every rendered object visible in the resource graph, diffable, and enforceable by policy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;helm.sh/v3.Chart&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Same client-side rendering approach, the previous major version&lt;/td&gt;
&lt;td&gt;Existing usage; Pulumi has said it expects to deprecate this in a future release in favor of &lt;code&gt;v4.Chart&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;helm.sh/v3.Release&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Embeds the real Helm SDK and creates an actual Helm release, including hooks; can import chart releases installed via the Helm CLI&lt;/td&gt;
&lt;td&gt;Charts that rely on Helm hooks, or environments already managed with the Helm CLI&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The practical distinction: &lt;code&gt;Chart&lt;/code&gt; resources give you a resource graph you can diff and enforce policy against, one object at a time, but no Helm hooks and no CLI interop. &lt;code&gt;Release&lt;/code&gt; gives you full Helm behavior, but policy enforcement can&amp;rsquo;t reach into the resources a Helm release creates.&lt;/p&gt;
&lt;p&gt;For CRDs, &lt;code&gt;crd2pulumi&lt;/code&gt; reads a CustomResourceDefinition&amp;rsquo;s OpenAPI schema and generates strongly typed classes in your language, so custom resources get IDE autocompletion and compile-time checking instead of the untyped &lt;code&gt;apiextensions.CustomResource&lt;/code&gt; path. For plain manifests and Kustomize output, current guidance favors the versioned APIs — &lt;code&gt;yaml/v2.ConfigFile&lt;/code&gt;, &lt;code&gt;yaml/v2.ConfigGroup&lt;/code&gt;, and &lt;code&gt;kustomize/v2.Directory&lt;/code&gt; — over their unversioned predecessors.&lt;/p&gt;
&lt;h2 id="how-do-you-manage-kubernetes-infrastructure-with-pulumi"&gt;How do you manage Kubernetes infrastructure with Pulumi?&lt;/h2&gt;
&lt;p&gt;A typical workflow for standing up an application on Kubernetes with Pulumi looks like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Install the Pulumi CLI and create a new project with &lt;code&gt;pulumi new kubernetes-typescript&lt;/code&gt; (or the &lt;code&gt;-python&lt;/code&gt;, &lt;code&gt;-go&lt;/code&gt;, &lt;code&gt;-csharp&lt;/code&gt;, or &lt;code&gt;-java&lt;/code&gt; variant for your language).&lt;/li&gt;
&lt;li&gt;Configure the target cluster context — either point Pulumi at an existing &lt;code&gt;kubeconfig&lt;/code&gt;, or provision the cluster itself in the same program using your cloud provider&amp;rsquo;s Pulumi provider (EKS, AKS, GKE, and so on).&lt;/li&gt;
&lt;li&gt;Import the Kubernetes provider package for your language and instantiate a &lt;code&gt;Provider&lt;/code&gt; resource if you need a non-default context or credentials.&lt;/li&gt;
&lt;li&gt;Define your workloads as typed resources — a &lt;code&gt;Deployment&lt;/code&gt;, a &lt;code&gt;Service&lt;/code&gt;, a &lt;code&gt;ConfigMap&lt;/code&gt; — using the same objects, loops, and functions you&amp;rsquo;d use in any other program in that language.&lt;/li&gt;
&lt;li&gt;For existing Helm charts, use &lt;code&gt;helm.sh/v4.Chart&lt;/code&gt; to render and manage the chart&amp;rsquo;s resources individually, or &lt;code&gt;helm.sh/v3.Release&lt;/code&gt; if you need full Helm hook support.&lt;/li&gt;
&lt;li&gt;For CustomResourceDefinitions, run &lt;code&gt;crd2pulumi&lt;/code&gt; against the CRD&amp;rsquo;s schema to generate typed classes, then instantiate custom resources the same way as built-in types.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;pulumi preview&lt;/code&gt; to see a diff of what will change, and &lt;code&gt;pulumi up&lt;/code&gt; to apply it — Server-Side Apply and the provider&amp;rsquo;s await logic handle upsert and readiness semantics automatically.&lt;/li&gt;
&lt;li&gt;Wire the stack into CI by running &lt;code&gt;pulumi preview&lt;/code&gt; on pull requests and &lt;code&gt;pulumi up&lt;/code&gt; on merge, using the same pipeline and secrets store your application code already uses.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here&amp;rsquo;s step 4 in TypeScript, deploying a Deployment and a Service together in one program:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="kr"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;k8s&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;@pulumi/kubernetes&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;appLabels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;my-service&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;deployment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;k8s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;v1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Deployment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;my-service&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;spec&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;selector&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;matchLabels&lt;/span&gt;: &lt;span class="kt"&gt;appLabels&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;replicas&lt;/span&gt;: &lt;span class="kt"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;labels&lt;/span&gt;: &lt;span class="kt"&gt;appLabels&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;spec&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;containers&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;my-service&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;my-registry/my-service:1.4.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;ports&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="nx"&gt;containerPort&lt;/span&gt;: &lt;span class="kt"&gt;8080&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;k8s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;v1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Service&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;my-service&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;spec&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;selector&lt;/span&gt;: &lt;span class="kt"&gt;appLabels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;ports&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="nx"&gt;port&lt;/span&gt;: &lt;span class="kt"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;targetPort&lt;/span&gt;: &lt;span class="kt"&gt;8080&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;LoadBalancer&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;serviceIp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;loadBalancer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ingress&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And the same shape in Python:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pulumi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pulumi_kubernetes&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;k8s&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;app_labels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;app&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;my-service&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;deployment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;k8s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;apps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Deployment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;my-service&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;k8s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;apps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DeploymentSpecArgs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;selector&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;k8s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LabelSelectorArgs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;match_labels&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;app_labels&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;replicas&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;k8s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PodTemplateSpecArgs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;k8s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ObjectMetaArgs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;labels&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;app_labels&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;k8s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PodSpecArgs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;containers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;k8s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContainerArgs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;my-service&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;my-registry/my-service:1.4.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ports&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k8s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContainerPortArgs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;container_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;]),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;k8s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Service&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;my-service&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;k8s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServiceSpecArgs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;selector&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;app_labels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ports&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k8s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServicePortArgs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;LoadBalancer&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;export&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;service_ip&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load_balancer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ingress&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Because &lt;code&gt;deployment&lt;/code&gt; and &lt;code&gt;service&lt;/code&gt; are ordinary values in the program, Pulumi resolves their dependency order automatically — there&amp;rsquo;s no separate apply stage to sequence by hand, even if the cluster itself were provisioned earlier in the same program.&lt;/p&gt;
&lt;h2 id="how-do-you-test-kubernetes-infrastructure-code-before-it-ships"&gt;How do you test Kubernetes infrastructure code before it ships?&lt;/h2&gt;
&lt;p&gt;Testing is one of the sharpest differences between the two approaches, and it&amp;rsquo;s worth being precise about what each side actually offers:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Capability&lt;/th&gt;
&lt;th&gt;Terraform&lt;/th&gt;
&lt;th&gt;Pulumi&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Unit tests (no cloud calls)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.tftest.hcl&lt;/code&gt; files with &lt;code&gt;command = plan&lt;/code&gt;, plus &lt;code&gt;mock_provider&lt;/code&gt; (GA since v1.7.0) for provider-free assertions&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pulumi.runtime.setMocks&lt;/code&gt; (TypeScript), &lt;code&gt;pulumi.runtime.set_mocks&lt;/code&gt; (Python), or &lt;code&gt;pulumi.WithMocks&lt;/code&gt; (Go), run inside the same test framework as application code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Integration tests (real infra)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.tftest.hcl&lt;/code&gt; files with &lt;code&gt;command = apply&lt;/code&gt; against a real or ephemeral environment&lt;/td&gt;
&lt;td&gt;Automation API drives real preview/up/destroy cycles against ephemeral stacks, orchestrated from your own code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Test language&lt;/td&gt;
&lt;td&gt;A second, declarative, test-specific language (&lt;code&gt;.tftest.hcl&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;The same language and test runner already used for application code — Jest, pytest, &lt;code&gt;go test&lt;/code&gt;, xUnit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Policy enforcement&lt;/td&gt;
&lt;td&gt;Sentinel or OPA/Rego, evaluated against the plan&lt;/td&gt;
&lt;td&gt;Pulumi Policies (policy packs in TypeScript/JavaScript or Python) or OPA/Rego via &lt;code&gt;pulumi-policy-opa&lt;/code&gt;, evaluated against the resource graph of a stack written in any Pulumi language&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;terraform test&lt;/code&gt; with &lt;code&gt;mock_provider&lt;/code&gt; is a legitimate, GA capability, and teams that have standardized on HCL testing patterns can write real infrastructure-free unit tests with it. The practical difference isn&amp;rsquo;t &amp;ldquo;Terraform can&amp;rsquo;t test&amp;rdquo; — it&amp;rsquo;s that Terraform tests live in a separate language and toolchain from the application code they support, while Pulumi&amp;rsquo;s tests run in the same Jest, pytest, or &lt;code&gt;go test&lt;/code&gt; suite a team already runs on every commit. Because Kubernetes objects created through &lt;code&gt;Chart&lt;/code&gt;, &lt;code&gt;ConfigGroup&lt;/code&gt;, or &lt;code&gt;CustomResource&lt;/code&gt; resources appear individually in Pulumi&amp;rsquo;s resource graph, Pulumi Policies apply to them the same way they&amp;rsquo;d apply to a cloud resource — there&amp;rsquo;s no separately named &amp;ldquo;Kubernetes policy&amp;rdquo; product, only the same policy-as-code engine applied to whatever&amp;rsquo;s in the graph.&lt;/p&gt;
&lt;h2 id="where-do-ai-agents-fit-into-kubernetes-infrastructure-management"&gt;Where do AI agents fit into Kubernetes infrastructure management?&lt;/h2&gt;
&lt;p&gt;An AI agent operating on infrastructure needs to read the current state, propose a change, preview its effect, and — ideally — get that change reviewed like any other pull request. That loop is much easier for an agent to execute reliably against a TypeScript or Python program than against HCL or raw YAML, because the agent can use the same static analysis, type checking, and test suite a developer would use, rather than reasoning about a domain-specific configuration language from scratch.&lt;/p&gt;
&lt;p&gt;Pulumi Neo is built for exactly this loop: an infrastructure agent with organizational context, policy guardrails, and human-in-the-loop approvals, working inside the same preview/policy/apply cycle described above. We cover the fuller picture of provisioning and governing agent workloads on Kubernetes — GPU-aware scheduling, session state, and the security boundary around what an agent is allowed to touch — in &lt;a href="https://www.pulumi.com/blog/ai-agents-on-kubernetes/"&gt;How to Run AI Agents on Kubernetes with Pulumi&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="when-is-terraform-still-the-right-choice-for-kubernetes"&gt;When is Terraform still the right choice for Kubernetes?&lt;/h2&gt;
&lt;p&gt;Fair comparisons cut both ways, and there are good reasons a team keeps using Terraform for Kubernetes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Provider and module catalog breadth.&lt;/strong&gt; Terraform&amp;rsquo;s registry has the largest catalog of providers and community modules of any IaC tool, and that matters for teams integrating many third-party systems alongside Kubernetes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A single tool across the whole estate.&lt;/strong&gt; Teams that have already standardized on Terraform for networking, IAM, and managed services often prefer one state model and one pipeline rather than introducing a second tool solely for Kubernetes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HCL&amp;rsquo;s simplicity for static configuration.&lt;/strong&gt; For infrastructure that&amp;rsquo;s genuinely declarative and doesn&amp;rsquo;t need loops, abstraction, or conditional logic, HCL reads and reviews cleanly.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Existing governance investment.&lt;/strong&gt; Teams with mature Sentinel or OPA policy libraries and deep in-house HCL expertise have real switching costs, and those costs are legitimate inputs to the decision.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The plan-time limitation is manageable in practice.&lt;/strong&gt; Once cluster provisioning and workload deployment are split into separate applies — HashiCorp&amp;rsquo;s own documented pattern — the &lt;code&gt;kubernetes_manifest&lt;/code&gt; plan-time requirement stops being a daily obstacle.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of that is a reason to avoid Terraform outright; it&amp;rsquo;s a reason to be clear-eyed about which parts of your Kubernetes workflow benefit most from a general-purpose language and which don&amp;rsquo;t.&lt;/p&gt;
&lt;h2 id="how-do-you-move-kubernetes-infrastructure-from-terraform-to-pulumi"&gt;How do you move Kubernetes infrastructure from Terraform to Pulumi?&lt;/h2&gt;
&lt;p&gt;Teams don&amp;rsquo;t have to choose all-or-nothing. Pulumi&amp;rsquo;s &lt;code&gt;pulumi convert&lt;/code&gt; command translates existing Terraform HCL into a Pulumi program in your target language as a starting point for further editing, and Pulumi&amp;rsquo;s guide to &lt;a href="https://www.pulumi.com/docs/iac/guides/migration/migrating-to-pulumi/from-kubernetes/"&gt;migrating Kubernetes YAML and Helm charts&lt;/a&gt; and its &lt;a href="https://www.pulumi.com/docs/iac/guides/migration/migrating-to-pulumi/from-terraform/"&gt;Terraform migration guide&lt;/a&gt; both cover the mechanics in more depth.&lt;/p&gt;
&lt;p&gt;Pulumi also now meets Terraform-standardized teams partway: Pulumi IaC supports native HCL as a language option, Pulumi Cloud can serve as a state backend for existing Terraform configurations, and Pulumi supports consuming Terraform modules directly. That coexistence story — not a forced rewrite — is often the more realistic starting point for a team with a large existing Terraform estate, including Kubernetes-adjacent infrastructure. Wiz, for example, uses Pulumi&amp;rsquo;s Automation API to manage more than 1 million cloud resources, including tens of thousands of Kubernetes clusters across hundreds of data centers, with more than 100,000 daily infrastructure updates.&lt;/p&gt;
&lt;h2 id="frequently-asked-questions"&gt;Frequently asked questions&lt;/h2&gt;
&lt;p&gt;Common questions that come up when teams evaluate Terraform and Pulumi for Kubernetes specifically.&lt;/p&gt;
&lt;h3 id="can-terraform-manage-a-kubernetes-cluster-and-its-workloads-in-one-apply"&gt;Can Terraform manage a Kubernetes cluster and its workloads in one apply?&lt;/h3&gt;
&lt;p&gt;Not reliably, per HashiCorp&amp;rsquo;s own documentation. The &lt;code&gt;kubernetes_manifest&lt;/code&gt; resource needs live API access at plan time, so the cluster must already exist and be reachable before Terraform can plan workload resources against it. The documented pattern is to split cluster provisioning and workload deployment into separate &lt;code&gt;apply&lt;/code&gt; operations and pass values between them with data sources or remote state.&lt;/p&gt;
&lt;h3 id="does-pulumi-replace-kubectl-and-helm"&gt;Does Pulumi replace kubectl and Helm?&lt;/h3&gt;
&lt;p&gt;No. Pulumi&amp;rsquo;s Kubernetes provider applies the Kubernetes API the same way &lt;code&gt;kubectl&lt;/code&gt; and Helm do — it doesn&amp;rsquo;t replace the cluster API or the Helm packaging format. What it replaces is the imperative or templated workflow around applying manifests: instead of running &lt;code&gt;kubectl apply&lt;/code&gt; or &lt;code&gt;helm install&lt;/code&gt; by hand or from a shell script, you declare the desired state in a Pulumi program and let &lt;code&gt;pulumi up&lt;/code&gt; reconcile it, with the same preview, diff, and rollback behavior Pulumi provides for any other cloud resource.&lt;/p&gt;
&lt;h3 id="is-terraforms-kubernetes-provider-actively-maintained"&gt;Is Terraform&amp;rsquo;s Kubernetes provider actively maintained?&lt;/h3&gt;
&lt;p&gt;Yes. The &lt;code&gt;hashicorp/kubernetes&lt;/code&gt; provider is on major version 3 (v3.2.1 as of this writing, with v3.0.0 shipping in December 2025) and requires Terraform 1.0.0 or later. It&amp;rsquo;s a first-party HashiCorp provider with regular releases.&lt;/p&gt;
&lt;h3 id="whats-the-difference-between-kubernetes_manifest-and-the-community-kubectl-provider"&gt;What&amp;rsquo;s the difference between kubernetes_manifest and the community kubectl provider?&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;kubernetes_manifest&lt;/code&gt;, from HashiCorp&amp;rsquo;s own provider, needs the cluster&amp;rsquo;s API reachable at plan time and is recommended mainly for custom resources not yet modeled as typed resources. The community &lt;code&gt;alekc/kubectl&lt;/code&gt; provider (a maintained fork of &lt;code&gt;gavinbunney/kubectl&lt;/code&gt;) applies free-form YAML through its &lt;code&gt;kubectl_manifest&lt;/code&gt; resource and is a common workaround for the plan-time requirement; recent versions also support Terraform&amp;rsquo;s ephemeral resources so sensitive values don&amp;rsquo;t have to be written to state.&lt;/p&gt;
&lt;h3 id="can-pulumi-and-terraform-manage-the-same-kubernetes-cluster-at-once"&gt;Can Pulumi and Terraform manage the same Kubernetes cluster at once?&lt;/h3&gt;
&lt;p&gt;Yes, in the sense that both talk to the same Kubernetes API and don&amp;rsquo;t inherently conflict — the risk is the same one you&amp;rsquo;d have with any two tools managing overlapping objects: whichever tool last wrote a resource&amp;rsquo;s desired state &amp;ldquo;owns&amp;rdquo; it until the other reconciles again. Most teams that run a mixed estate assign ownership by namespace, resource type, or workload boundary rather than letting both tools manage the same objects.&lt;/p&gt;
&lt;h2 id="where-to-go-next"&gt;Where to go next&lt;/h2&gt;
&lt;p&gt;Related reading on Pulumi and Kubernetes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/yaml-terraform-pulumi-whats-the-smart-choice-for-deployment-automation-with-kubernetes/"&gt;YAML, Terraform, or Pulumi: what&amp;rsquo;s the smart choice for deployment automation with Kubernetes?&lt;/a&gt; — the manifest-authoring comparison this guide deliberately doesn&amp;rsquo;t repeat&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/ai-agents-on-kubernetes/"&gt;How to Run AI Agents on Kubernetes with Pulumi&lt;/a&gt; — provisioning and governing infrastructure for agent workloads specifically&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/what-is/infrastructure-as-code-for-kubernetes/"&gt;Kubernetes infrastructure as code: tools and best practices&lt;/a&gt; — a broader look at the IaC options for Kubernetes&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/docs/iac/guides/testing/unit/"&gt;Unit testing Pulumi programs&lt;/a&gt; — the full mock API reference across languages&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/docs/integrations/clouds/kubernetes/crd2pulumi/"&gt;crd2pulumi&lt;/a&gt; — generating typed SDKs from CustomResourceDefinitions&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/docs/iac/guides/migration/migrating-to-pulumi/from-terraform/"&gt;Migrating to Pulumi from Terraform&lt;/a&gt; and &lt;a href="https://www.pulumi.com/docs/iac/guides/migration/migrating-to-pulumi/from-kubernetes/"&gt;from Kubernetes YAML and Helm&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/registry/packages/kubernetes/"&gt;Pulumi&amp;rsquo;s Kubernetes provider in the registry&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><author>Pulumi Content Team</author><category>kubernetes</category><category>terraform</category><category>infrastructure-as-code</category><category>platform-engineering</category></item><item><title>Best Terraform Alternatives in 2026</title><link>https://www.pulumi.com/blog/best-terraform-alternatives/</link><pubDate>Sat, 18 Jul 2026 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/best-terraform-alternatives/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/best-terraform-alternatives/index.png" /&gt;
&lt;p&gt;The strongest Terraform alternatives in 2026 fall into three groups: general-purpose-language platforms like Pulumi and AWS CDK, HCL-compatible forks like OpenTofu, and cloud-specific tools like AWS CloudFormation, Azure Bicep, and Crossplane. Which one fits depends less on syntax preference than on how well it lets your team, and your AI coding agents, read, test, and change infrastructure safely.&lt;/p&gt;
&lt;p&gt;What&amp;rsquo;s changed since this list was last useful: HashiCorp archived CDK for Terraform in December 2025, OpenTofu shipped its 1.12 release in May 2026, and Pulumi now runs the same HCL files Terraform does, so authoring format and deployment engine are separate decisions.&lt;/p&gt;
&lt;h2 id="why-teams-are-re-evaluating-terraform-in-2026"&gt;Why teams are re-evaluating Terraform in 2026&lt;/h2&gt;
&lt;p&gt;Terraform has been the default infrastructure-as-code tool for most of the last decade, and for good reason: a mature provider ecosystem, a large community, and a state model that most platform teams have learned to live with. But three forces are pushing teams to look again at what else is available.&lt;/p&gt;
&lt;p&gt;The first is licensing. HashiCorp moved Terraform from the open-source Mozilla Public License to the Business Source License in August 2023, a shift that triggered the community fork now known as OpenTofu. HashiCorp itself became a wholly owned subsidiary of IBM when that acquisition closed in February 2025. Neither event breaks anything for existing Terraform users, but both changed how governance and long-term product direction get decided, and that&amp;rsquo;s enough for some platform teams to want a documented Plan B.&lt;/p&gt;
&lt;p&gt;The second is the accumulated cost of working in a domain-specific language. HCL wasn&amp;rsquo;t designed for the abstraction, composition, and testing patterns that platform engineering now expects: sharing logic across teams, writing meaningful unit tests, and building internal libraries that read like software rather than templated configuration. Terraform has closed some of this gap over time, adding a native test framework in version 1.6, but the ceiling on what a declarative DSL can express is still lower than what a general-purpose language offers.&lt;/p&gt;
&lt;p&gt;The third, and the one reshaping the conversation fastest, is agentic AI. Coding agents like GitHub Copilot, Claude, and Pulumi&amp;rsquo;s own Neo are now a standing part of how engineering teams write and review code, and infrastructure is not exempt. These agents were trained on enormous volumes of real Python, TypeScript, Go, and Java, plus their entire testing and tooling ecosystems, but on comparatively little well-formed HCL. When an agent can write and modify infrastructure in a language it already understands deeply, the feedback loop of propose, test, preview, and ship gets tighter. When it has to reason about a purpose-built DSL, that loop lengthens and the agent is more likely to produce configuration nobody would write by hand. That gap doesn&amp;rsquo;t disqualify Terraform, but it does mean &amp;ldquo;which IaC tool works best with AI agents&amp;rdquo; is now a first-class question when teams evaluate alternatives, alongside the perennial ones about multi-cloud reach and governance.&lt;/p&gt;
&lt;p&gt;None of this is an argument that Terraform is going away. It remains a capable, widely adopted tool with a genuinely large provider catalog. It&amp;rsquo;s an argument that 2026 is a reasonable moment to look at what else exists, honestly, and pick the tool that fits where your infrastructure and your engineering workflow are actually headed. For a broader look at how AI agents are changing infrastructure work generally, see &lt;a href="https://www.pulumi.com/releases/agentic-infrastructure-era/"&gt;building for agentic infrastructure&lt;/a&gt;. For a head-to-head look at Pulumi and Terraform specifically, see our &lt;a href="https://www.pulumi.com/docs/iac/comparisons/terraform/"&gt;Pulumi vs. Terraform comparison&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="do-you-have-to-leave-hcl-behind"&gt;Do you have to leave HCL behind?&lt;/h2&gt;
&lt;p&gt;No, and that&amp;rsquo;s a meaningful change since this guide was last written. Authoring format and deployment engine used to be a single choice: pick Terraform, and HCL and the Terraform CLI came as a bundle. Two things Pulumi shipped as generally available in August 2026 split that bundle apart. Staying on HCL without adopting Pulumi is also a real option, and the &lt;a href="#opentofu"&gt;OpenTofu section&lt;/a&gt; below covers that path.&lt;/p&gt;
&lt;p&gt;The first is &lt;a href="https://www.pulumi.com/docs/iac/languages-sdks/hcl/"&gt;Pulumi HCL&lt;/a&gt;, which runs the same &lt;code&gt;.tf&lt;/code&gt; files you&amp;rsquo;d write for Terraform or OpenTofu, unmodified, on Pulumi&amp;rsquo;s engine. A project is a &lt;code&gt;Pulumi.yaml&lt;/code&gt; with &lt;code&gt;runtime: hcl&lt;/code&gt; and one or more &lt;code&gt;.tf&lt;/code&gt; files, needs Pulumi CLI 3.256.0 or later, and resolves providers against the OpenTofu registry by default. It isn&amp;rsquo;t a perfect emulation: &lt;code&gt;backend&lt;/code&gt;, &lt;code&gt;provider_meta&lt;/code&gt;, &lt;code&gt;required_version&lt;/code&gt;, and &lt;code&gt;experiments&lt;/code&gt; blocks are accepted but ignored with a warning, a &lt;code&gt;cloud&lt;/code&gt; block is an error, existing Terraform state files aren&amp;rsquo;t read directly (bring resources over with &lt;code&gt;pulumi import --from hcl&lt;/code&gt;), and provisioner &lt;code&gt;connection&lt;/code&gt; blocks support SSH only.&lt;/p&gt;
&lt;p&gt;The second is &lt;a href="https://www.pulumi.com/docs/iac/get-started/terraform/terraform-state-backend/"&gt;Pulumi Cloud as a Terraform and OpenTofu state backend&lt;/a&gt;, which goes the other direction: keep the Terraform or OpenTofu CLI exactly as it is, and point its &lt;code&gt;backend &amp;quot;remote&amp;quot;&lt;/code&gt; block at &lt;code&gt;tf.pulumi.com&lt;/code&gt; for encrypted state storage, locking, update history, and Insights visibility. This also has real limits worth stating plainly: drift detection isn&amp;rsquo;t available for Terraform-managed stacks (it requires a Pulumi program), migrating from HCP Terraform state is a manual export and push rather than an automatic switch, and update diffs are derived from state rather than a live &lt;code&gt;terraform plan&lt;/code&gt;, so they&amp;rsquo;re best-effort.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;If you want to keep&amp;hellip;&lt;/th&gt;
&lt;th&gt;Change this&amp;hellip;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Your &lt;code&gt;.tf&lt;/code&gt; files and HCL syntax&lt;/td&gt;
&lt;td&gt;Nothing — run them on Pulumi&amp;rsquo;s engine with &lt;code&gt;runtime: hcl&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Your Terraform or OpenTofu CLI and workflow&lt;/td&gt;
&lt;td&gt;Only the backend, pointed at Pulumi Cloud&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Neither, and want a general-purpose language&lt;/td&gt;
&lt;td&gt;Everything — see the &lt;a href="https://www.pulumi.com/docs/iac/comparisons/terraform/"&gt;Pulumi vs. Terraform comparison&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;For the mechanics of each path, see &lt;a href="https://www.pulumi.com/blog/terraform-to-pulumi-cloud-hands-on/"&gt;terraform-to-pulumi-cloud-hands-on&lt;/a&gt;, &lt;a href="https://www.pulumi.com/blog/terraforms-data-model-on-pulumis-engine/"&gt;how Pulumi&amp;rsquo;s engine models Terraform&amp;rsquo;s data model&lt;/a&gt;, and &lt;a href="https://www.pulumi.com/blog/compatibility-testing-pulumi-hcl/"&gt;how Pulumi HCL is tested for OpenTofu compatibility&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="pulumi"&gt;Pulumi&lt;/h2&gt;
&lt;p&gt;Pulumi is an infrastructure-as-code platform that lets you define cloud infrastructure in general-purpose languages, including Python, TypeScript, JavaScript, Go, .NET, and Java, plus YAML and HCL for teams that prefer a declarative format. Rather than compiling down to another tool&amp;rsquo;s templates, Pulumi programs run directly against a deployment engine that supports hundreds of providers in total, covering AWS, Azure, Google Cloud, Kubernetes, and a long tail of SaaS and on-prem targets.&lt;/p&gt;
&lt;p&gt;Pulumi also supports HCL as a first-class language, and Pulumi Cloud can serve as a drop-in &lt;a href="https://www.pulumi.com/docs/iac/get-started/terraform/terraform-state-backend/"&gt;state backend for existing Terraform code&lt;/a&gt;. That means teams with a large, working Terraform estate aren&amp;rsquo;t required to rewrite anything to get Pulumi Cloud&amp;rsquo;s state management, access controls, and policy enforcement — they can point existing HCL at Pulumi with minimal changes, then migrate configuration into a general-purpose language on their own timeline rather than all at once.&lt;/p&gt;
&lt;p&gt;Writing infrastructure in a real language means you get the tooling that comes with it for free: IDE autocomplete and type checking, unit and integration test frameworks, package managers for sharing reusable components, and the same code review and CI/CD conventions your application teams already use. It also means Pulumi programs run directly through Pulumi&amp;rsquo;s own deployment engine against any cloud or SaaS platform, with no separate compile-to-template step in between, unlike CDK-style tools that transpile into another system&amp;rsquo;s format before anything deploys. For an AI agent iterating on infrastructure, fewer steps between &amp;ldquo;write code&amp;rdquo; and &amp;ldquo;see the result&amp;rdquo; means a tighter feedback loop. &lt;a href="https://www.pulumi.com/product/neo/"&gt;Pulumi Neo&lt;/a&gt;, the platform&amp;rsquo;s infrastructure agent, builds on this directly: it can propose Terraform-to-Pulumi migrations, run policy-checked previews, respond to failures, and open pull requests inside a team&amp;rsquo;s existing review workflow.&lt;/p&gt;
&lt;p&gt;For platform teams, Pulumi adds built-in guardrails that Terraform typically requires bolting on through third-party tooling: &lt;a href="https://www.pulumi.com/what-is/what-is-policy-as-code/"&gt;policy as code&lt;/a&gt; for enforcing organizational rules before a deployment ships, a secrets and configuration management layer (ESC), human-in-the-loop approval gates, and reusable components that teams can publish and consume like any other internal library, in whatever language they use. The tradeoff is ecosystem maturity in the opposite direction from Terraform: Pulumi&amp;rsquo;s provider catalog, while broad, has a shorter history than Terraform&amp;rsquo;s for a handful of niche or long-tail providers, and some teams will need to weigh that against the language and workflow benefits.&lt;/p&gt;
&lt;p&gt;At scale, Pulumi customers report concrete outcomes. BMW&amp;rsquo;s Software Factory manages more than 20,000 cloud resources with Python-based infrastructure code. Wiz manages more than a million cloud resources through Pulumi&amp;rsquo;s Automation API and Kubernetes operator, pushing hundreds of thousands of daily infrastructure updates across hundreds of data centers. Supabase scaled from a single region to 16 regions and roughly 80,000 resources. Atlassian&amp;rsquo;s Bitbucket team reported a 50% reduction in infrastructure maintenance time after adopting Pulumi.&lt;/p&gt;
&lt;h2 id="opentofu"&gt;OpenTofu&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.pulumi.com/what-is/opentofu-vs-terraform/"&gt;OpenTofu&lt;/a&gt; is an open-source fork of Terraform, governed by the Linux Foundation rather than a single vendor. It emerged directly from HashiCorp&amp;rsquo;s August 2023 license change: a group of Terraform users and vendors forked the last MPL-licensed Terraform release and committed to keeping the fork under a permissive open-source license going forward.&lt;/p&gt;
&lt;p&gt;The practical pitch is continuity: OpenTofu aims to stay a close drop-in replacement for Terraform, using the same HCL syntax, the same provider ecosystem (most Terraform providers work unmodified), and largely the same workflow, so teams can migrate with minimal rewriting. OpenTofu&amp;rsquo;s own FAQ notes the compatibility boundary directly: it works with state files created by Terraform up through the 1.5.x line, the last release before HashiCorp&amp;rsquo;s license change.&lt;/p&gt;
&lt;p&gt;Since forking, OpenTofu&amp;rsquo;s maintainers have also shipped features HashiCorp hasn&amp;rsquo;t, including state encryption, provider-defined functions, &lt;code&gt;for_each&lt;/code&gt; on provider blocks, OCI registry support for providers and modules, and, in the &lt;a href="https://opentofu.org/blog/opentofu-1-12-0/"&gt;1.12 release from May 2026&lt;/a&gt;, dynamic &lt;code&gt;prevent_destroy&lt;/code&gt; values. The project runs under the Linux Foundation and was accepted into the CNCF Sandbox in April 2025; its &lt;a href="https://search.opentofu.org/"&gt;registry&lt;/a&gt; lists 4,000+ providers and 22,000+ modules. It remains MPL-2.0 licensed.&lt;/p&gt;
&lt;p&gt;The tradeoff is that OpenTofu inherits HCL&amp;rsquo;s ceiling along with its familiarity. It solves the licensing and governance concern cleanly, but it doesn&amp;rsquo;t address the testing, composability, or general-purpose-language advantages that come with moving to a platform like Pulumi or AWS CDK, and AI coding agents face the same reasoning gap with OpenTofu&amp;rsquo;s HCL that they do with Terraform&amp;rsquo;s.&lt;/p&gt;
&lt;h2 id="aws-cloudformation"&gt;AWS CloudFormation&lt;/h2&gt;
&lt;p&gt;CloudFormation is AWS&amp;rsquo;s native infrastructure-as-code service, using YAML or JSON templates that AWS itself parses, validates, and rolls back on failure. Because it&amp;rsquo;s built and operated by AWS, it gets first access to new AWS service features, and its state is managed entirely by AWS rather than a separate backend your team has to configure and secure.&lt;/p&gt;
&lt;p&gt;The obvious limit is scope: CloudFormation only provisions AWS resources, so any team running infrastructure across more than one cloud, or alongside Kubernetes and SaaS resources, will need a second tool for everything outside AWS. Authoring in raw YAML or JSON also means the same testing and abstraction limitations that HCL-based tools face, without even Terraform&amp;rsquo;s ecosystem of community modules to offset it. Many AWS-focused teams increasingly write CloudFormation stacks through AWS CDK rather than by hand.&lt;/p&gt;
&lt;h2 id="aws-cdk"&gt;AWS CDK&lt;/h2&gt;
&lt;p&gt;AWS CDK (Cloud Development Kit) lets you define AWS infrastructure using general-purpose languages including TypeScript, JavaScript, Python, Java, C#, and Go. Under the hood, CDK code compiles down to standard CloudFormation templates, which AWS then deploys the same way it deploys any other CloudFormation stack.&lt;/p&gt;
&lt;p&gt;CDK gives AWS-only teams most of the general-purpose-language benefits that Pulumi offers: real loops, functions, tests, and packages, plus the IDE support that comes with a mainstream language. The compile step is the notable difference from a platform like Pulumi, which runs your program directly against cloud provider APIs: CDK synthesizes a CloudFormation template first, then hands that off, which adds a layer between what your code says and what gets deployed, and can slow the feedback loop an AI coding agent depends on when it&amp;rsquo;s proposing and testing changes iteratively. CDK is also AWS-only, so it doesn&amp;rsquo;t help teams who need one consistent authoring model across multiple clouds. AWS CDK is actively developed and unaffected by HashiCorp&amp;rsquo;s decision to sunset its own, differently named, CDK for Terraform — see the next section.&lt;/p&gt;
&lt;h2 id="cdk-for-terraform-cdktf-is-archived-not-an-alternative"&gt;CDK for Terraform (CDKTF) is archived, not an alternative&lt;/h2&gt;
&lt;p&gt;CDK for Terraform used to appear on lists like this one as a way to write Terraform infrastructure in TypeScript, Python, Java, C#, or Go instead of HCL. That&amp;rsquo;s no longer a live option. HashiCorp &lt;a href="https://github.com/hashicorp/terraform-cdk"&gt;archived the project on December 10, 2025&lt;/a&gt;, stating in its own sunset notice that CDKTF &amp;ldquo;did not find product-market fit at scale&amp;rdquo; and that it would &amp;ldquo;focus its investments on Terraform core and its broader ecosystem&amp;rdquo; instead. The GitHub repository is now read-only, and per HashiCorp&amp;rsquo;s FAQ, &amp;ldquo;no further updates, fixes, or improvements (including compatibility updates) will be made.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;CDKTF is MPL-licensed, so existing code keeps running and community forks are technically possible, but there&amp;rsquo;s no maintainer, no security patching, and no provider compatibility work going forward. Teams currently on CDKTF have two realistic paths: move to native HCL with OpenTofu or Terraform, or move to a maintained general-purpose-language platform. Pulumi supports both directions — HCL directly, or Python, TypeScript, Go, C#, and Java for a full migration — and Pulumi&amp;rsquo;s own &lt;a href="https://www.pulumi.com/blog/cdktf-is-deprecated-whats-next-for-your-team/"&gt;teardown of the sunset and what to do next&lt;/a&gt; covers the migration paths in more depth than fits here.&lt;/p&gt;
&lt;p&gt;One disambiguation worth stating plainly, since the two names are similar: AWS CDK and CDK for Terraform are separate projects with different maintainers and different targets. HashiCorp built CDKTF in collaboration with the AWS CDK team, on the same construct model, but it generated Terraform configuration rather than CloudFormation. AWS CDK targets CloudFormation, is maintained by AWS, and continues to ship regular releases. Only CDKTF, HashiCorp&amp;rsquo;s Terraform-targeting CDK, is the one that&amp;rsquo;s archived.&lt;/p&gt;
&lt;h2 id="crossplane"&gt;Crossplane&lt;/h2&gt;
&lt;p&gt;Crossplane is a Kubernetes-native framework for infrastructure as code, letting platform teams define and provision cloud resources as Kubernetes custom resources, managed by controllers running inside a cluster. Rather than running a separate CLI-driven apply cycle, Crossplane treats the cluster&amp;rsquo;s control plane as the single source of truth for both application and infrastructure state.&lt;/p&gt;
&lt;p&gt;This model is a natural fit for teams already standardized on Kubernetes as their platform layer: infrastructure gets the same GitOps, RBAC, and reconciliation patterns as application workloads, and platform teams can build self-service abstractions (Crossplane calls these Compositions) that let application developers request infrastructure without learning Crossplane&amp;rsquo;s own resource model directly. The tradeoff is that Crossplane assumes a Kubernetes-centric operating model; teams without an existing cluster-based platform will be adopting Kubernetes as a prerequisite, not just an infrastructure tool, and Crossplane&amp;rsquo;s ecosystem of documented, named enterprise deployments is thinner than Terraform&amp;rsquo;s or Pulumi&amp;rsquo;s, making it harder to evaluate at-scale track record from public case studies alone.&lt;/p&gt;
&lt;h2 id="azure-bicep"&gt;Azure Bicep&lt;/h2&gt;
&lt;p&gt;Bicep is Microsoft&amp;rsquo;s domain-specific language for deploying Azure resources, designed as a cleaner authoring layer over Azure Resource Manager templates. Bicep code transpiles to ARM JSON, but with syntax that&amp;rsquo;s considerably easier to read and write than hand-authored ARM. It&amp;rsquo;s open source under the MIT license, and Microsoft&amp;rsquo;s own documentation recommends it over raw ARM JSON for new Azure-only infrastructure work.&lt;/p&gt;
&lt;p&gt;Like CloudFormation, Bicep&amp;rsquo;s scope is a single cloud, in this case Azure exclusively, so it isn&amp;rsquo;t a candidate for teams managing infrastructure across providers. It&amp;rsquo;s also a DSL rather than a general-purpose language, which means the same testing and reuse ceiling as HCL, though for teams fully committed to Azure and nothing else, it remains a well-supported, low-friction choice.&lt;/p&gt;
&lt;h2 id="ansible"&gt;Ansible&lt;/h2&gt;
&lt;p&gt;Ansible is an agentless configuration management and automation tool that uses YAML playbooks to describe the desired state of servers, software, and application deployments. Red Hat, which acquired Ansible in 2015 and was itself acquired by IBM in 2019, maintains it today as part of Red Hat Ansible Automation Platform.&lt;/p&gt;
&lt;p&gt;Ansible is often grouped with Terraform in &amp;ldquo;IaC tool&amp;rdquo; comparisons, but the two solve different problems and are frequently used together rather than as substitutes: Terraform or Pulumi provisions the resource, and Ansible then configures the operating system, installs software, and manages ongoing configuration state on top of it. Ansible does ship cloud provisioning modules and can create resources directly, but its design center is procedural configuration management, not declarative resource provisioning at the scale Terraform-class tools target. Teams evaluating Terraform alternatives for cloud provisioning specifically will usually want a tool from this list&amp;rsquo;s other categories, with Ansible layered in afterward for day-two configuration.&lt;/p&gt;
&lt;h2 id="terragrunt"&gt;Terragrunt&lt;/h2&gt;
&lt;p&gt;Terragrunt is a thin orchestration wrapper maintained by Gruntwork that sits on top of Terraform or OpenTofu, rather than replacing either one. Its job is keeping multi-environment, multi-module HCL configurations DRY, and coordinating the order in which modules apply across a larger infrastructure codebase.&lt;/p&gt;
&lt;p&gt;Terragrunt reached &lt;a href="https://www.gruntwork.io/blog/terragrunt-1-0-released"&gt;1.0 in March 2026&lt;/a&gt;, with the latest patch, 1.1.3, out in August 2026. The headline of 1.0 was a formal &lt;a href="https://terragrunt.gruntwork.io/docs/process/1-0-guarantees"&gt;backwards-compatibility guarantee&lt;/a&gt; covering CLI flags, HCL config, and command output for the entire 1.x line, rather than any new functionality, aimed at teams who&amp;rsquo;d been burned by breaking changes in earlier releases. Terragrunt Stacks, which let a single configuration generate multiple units, reached general availability earlier, in version 0.78. One detail worth knowing if you&amp;rsquo;re choosing between forks: Terragrunt now shells out to &lt;code&gt;tofu&lt;/code&gt; by default rather than &lt;code&gt;terraform&lt;/code&gt;, reflecting where the community around it has settled; that&amp;rsquo;s overridable via the &lt;code&gt;terraform_binary&lt;/code&gt; option. It&amp;rsquo;s still MIT licensed.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s worth being precise about what Terragrunt is not: it doesn&amp;rsquo;t introduce a new language, provider model, or state backend, and it doesn&amp;rsquo;t address HCL&amp;rsquo;s testing or abstraction limitations on its own. Terragrunt is a companion tool rather than a Terraform alternative, and teams adopt it to make Terraform or OpenTofu easier to operate at scale. If the underlying frustration is with HCL itself rather than with configuration sprawl, Terragrunt won&amp;rsquo;t resolve it, and it&amp;rsquo;s worth reading &lt;a href="https://www.pulumi.com/what-is/what-is-terragrunt/"&gt;what Terragrunt is and isn&amp;rsquo;t&lt;/a&gt; before assuming it solves a language problem.&lt;/p&gt;
&lt;h2 id="comparison-table"&gt;Comparison table&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Language&lt;/th&gt;
&lt;th&gt;Cloud coverage&lt;/th&gt;
&lt;th&gt;AI-agent readiness&lt;/th&gt;
&lt;th&gt;Governance &amp;amp; policy&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Pulumi&lt;/td&gt;
&lt;td&gt;Python, TypeScript, JavaScript, Go, .NET, Java, YAML, HCL&lt;/td&gt;
&lt;td&gt;hundreds of providers, any cloud&lt;/td&gt;
&lt;td&gt;High — real languages agents are trained on; Neo agent built in&lt;/td&gt;
&lt;td&gt;Policy as code, ESC secrets, human-in-the-loop approvals&lt;/td&gt;
&lt;td&gt;Teams standardizing on AI-native, multi-cloud engineering workflows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenTofu&lt;/td&gt;
&lt;td&gt;HCL&lt;/td&gt;
&lt;td&gt;Same provider ecosystem as Terraform&lt;/td&gt;
&lt;td&gt;Same as Terraform — DSL limits agent reasoning&lt;/td&gt;
&lt;td&gt;Linux Foundation / CNCF Sandbox; features like state encryption and dynamic &lt;code&gt;prevent_destroy&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Terraform users prioritizing open governance with minimal migration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AWS CloudFormation&lt;/td&gt;
&lt;td&gt;YAML/JSON&lt;/td&gt;
&lt;td&gt;AWS only&lt;/td&gt;
&lt;td&gt;Low — templated config, no native testing&lt;/td&gt;
&lt;td&gt;AWS-managed state and rollback&lt;/td&gt;
&lt;td&gt;Teams fully committed to AWS wanting a fully managed native service&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AWS CDK&lt;/td&gt;
&lt;td&gt;TypeScript, Python, Java, C#, Go&lt;/td&gt;
&lt;td&gt;AWS only (compiles to CloudFormation)&lt;/td&gt;
&lt;td&gt;Medium — real languages, but a compile step slows feedback&lt;/td&gt;
&lt;td&gt;Inherits CloudFormation governance&lt;/td&gt;
&lt;td&gt;AWS-only teams wanting general-purpose languages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CDK for Terraform (CDKTF)&lt;/td&gt;
&lt;td&gt;TypeScript, Python, Java, C#, Go&lt;/td&gt;
&lt;td&gt;Archived Dec. 2025 — not a viable choice&lt;/td&gt;
&lt;td&gt;N/A — no further updates of any kind&lt;/td&gt;
&lt;td&gt;HashiCorp, unmaintained&lt;/td&gt;
&lt;td&gt;Nothing new; existing users should plan a move&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Crossplane&lt;/td&gt;
&lt;td&gt;Kubernetes YAML/CRDs&lt;/td&gt;
&lt;td&gt;Any cloud, via Kubernetes control plane&lt;/td&gt;
&lt;td&gt;Medium — benefits from Kubernetes-native agent tooling&lt;/td&gt;
&lt;td&gt;RBAC and GitOps via Kubernetes&lt;/td&gt;
&lt;td&gt;Platform teams already standardized on Kubernetes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Azure Bicep&lt;/td&gt;
&lt;td&gt;Bicep DSL (compiles to ARM)&lt;/td&gt;
&lt;td&gt;Azure only&lt;/td&gt;
&lt;td&gt;Low — DSL, no general-purpose tooling&lt;/td&gt;
&lt;td&gt;Inherits Azure Resource Manager governance&lt;/td&gt;
&lt;td&gt;Azure-only teams wanting a cleaner ARM authoring layer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ansible&lt;/td&gt;
&lt;td&gt;YAML playbooks&lt;/td&gt;
&lt;td&gt;Any cloud, config-management focused&lt;/td&gt;
&lt;td&gt;Low for provisioning; not its design center&lt;/td&gt;
&lt;td&gt;Role-based playbooks, limited policy tooling&lt;/td&gt;
&lt;td&gt;Day-two configuration management alongside an IaC tool&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Terragrunt&lt;/td&gt;
&lt;td&gt;HCL (wraps Terraform/OpenTofu)&lt;/td&gt;
&lt;td&gt;Same as underlying Terraform/OpenTofu&lt;/td&gt;
&lt;td&gt;Same as underlying Terraform/OpenTofu&lt;/td&gt;
&lt;td&gt;Same as underlying Terraform/OpenTofu&lt;/td&gt;
&lt;td&gt;Keeping large Terraform/OpenTofu codebases DRY, not a language alternative&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="where-each-project-stands-in-august-2026"&gt;Where each project stands in August 2026&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Project&lt;/th&gt;
&lt;th&gt;Latest release&lt;/th&gt;
&lt;th&gt;License&lt;/th&gt;
&lt;th&gt;Governed by&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Terraform&lt;/td&gt;
&lt;td&gt;1.15.x&lt;/td&gt;
&lt;td&gt;Business Source License&lt;/td&gt;
&lt;td&gt;HashiCorp, an IBM Company&lt;/td&gt;
&lt;td&gt;Actively developed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenTofu&lt;/td&gt;
&lt;td&gt;1.12 (May 2026)&lt;/td&gt;
&lt;td&gt;MPL-2.0&lt;/td&gt;
&lt;td&gt;Linux Foundation; CNCF Sandbox&lt;/td&gt;
&lt;td&gt;Actively developed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Terragrunt&lt;/td&gt;
&lt;td&gt;1.1.3 (Aug. 2026)&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;td&gt;Gruntwork&lt;/td&gt;
&lt;td&gt;Actively developed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CDK for Terraform (CDKTF)&lt;/td&gt;
&lt;td&gt;Archived at sunset&lt;/td&gt;
&lt;td&gt;MPL-2.0&lt;/td&gt;
&lt;td&gt;None — unmaintained&lt;/td&gt;
&lt;td&gt;Archived Dec. 10, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pulumi&lt;/td&gt;
&lt;td&gt;Rolling releases; CLI 3.259.x&lt;/td&gt;
&lt;td&gt;Apache 2.0 (SDKs/CLI)&lt;/td&gt;
&lt;td&gt;Pulumi Corporation&lt;/td&gt;
&lt;td&gt;Actively developed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="how-to-choose"&gt;How to choose&lt;/h2&gt;
&lt;p&gt;If your team is standardizing on a single cloud and wants the deepest, most tightly integrated tooling for it, the native option, CloudFormation for AWS or Bicep for Azure, is a reasonable default, especially for smaller platform teams that don&amp;rsquo;t need cross-cloud abstraction. If you&amp;rsquo;re on AWS specifically and want general-purpose languages without leaving the CloudFormation ecosystem, AWS CDK is the more capable choice, with the caveat that its compile-then-deploy step adds a layer between code and infrastructure.&lt;/p&gt;
&lt;p&gt;If your team is already running Kubernetes as its platform layer and wants infrastructure provisioning to follow the same GitOps and RBAC model as application workloads, Crossplane is worth a serious look, understanding that it comes with Kubernetes as a hard dependency.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re deep in Terraform today, comfortable with HCL, and your primary concern is licensing and governance rather than language or workflow, OpenTofu is the lowest-friction path: same syntax, same providers, community governance instead of vendor governance.&lt;/p&gt;
&lt;p&gt;If your team is investing in AI coding agents as part of its engineering workflow, wants multi-cloud reach without maintaining separate tools per provider, and values real testing, packaging, and code review practices for infrastructure the same way it does for application code, Pulumi is built specifically for that combination. It&amp;rsquo;s also the most direct path for teams on the now-archived CDKTF who need an actively maintained general-purpose-language platform, and, for teams on Terraform who aren&amp;rsquo;t ready to leave HCL, it&amp;rsquo;s the only option here that lets you keep writing &lt;code&gt;.tf&lt;/code&gt; files while gaining that platform underneath them.&lt;/p&gt;
&lt;h2 id="frequently-asked-questions"&gt;Frequently asked questions&lt;/h2&gt;
&lt;h3 id="is-terraform-still-free-to-use"&gt;Is Terraform still free to use?&lt;/h3&gt;
&lt;p&gt;Yes, for most teams. Terraform&amp;rsquo;s core CLI and the vast majority of its providers remain free under the Business Source License that HashiCorp adopted in August 2023. The license restricts a narrow set of commercial use cases, mainly building a competing managed offering on top of Terraform, which doesn&amp;rsquo;t affect ordinary infrastructure teams using it internally.&lt;/p&gt;
&lt;h3 id="what-is-the-best-open-source-terraform-alternative"&gt;What is the best open-source Terraform alternative?&lt;/h3&gt;
&lt;p&gt;OpenTofu is the closest open-source alternative if you want to keep using HCL and the Terraform provider ecosystem, since it&amp;rsquo;s a direct fork governed by the Linux Foundation. If open-source licensing matters but you&amp;rsquo;re open to a different language, Pulumi&amp;rsquo;s SDKs and CLI are open source (Apache 2.0), and Crossplane is a CNCF project for teams standardized on Kubernetes.&lt;/p&gt;
&lt;h3 id="which-iac-tool-works-best-with-ai-coding-agents"&gt;Which IaC tool works best with AI coding agents?&lt;/h3&gt;
&lt;p&gt;Tools built on general-purpose languages give AI coding agents the strongest foundation, since those agents have far more training exposure to real Python, TypeScript, Go, C#, and Java than to any infrastructure-specific DSL. Pulumi and AWS CDK both fit this category; AWS CDK&amp;rsquo;s compile step to CloudFormation adds a layer of indirection that Pulumi, which runs directly against provider APIs, doesn&amp;rsquo;t have.&lt;/p&gt;
&lt;h3 id="can-i-migrate-from-terraform-without-rewriting-everything"&gt;Can I migrate from Terraform without rewriting everything?&lt;/h3&gt;
&lt;p&gt;It depends on which alternative you choose. Moving to OpenTofu requires essentially no rewriting, since it&amp;rsquo;s HCL-compatible by design. Moving to Pulumi can be just as light-touch, since Pulumi Cloud works as a Terraform state backend and Pulumi IaC speaks HCL natively; teams that do want to move into a general-purpose language, or that are adopting AWS CDK or a similar platform, will need to translate configuration into program code, though tools like Pulumi&amp;rsquo;s import and conversion tooling, and increasingly AI coding agents themselves, can automate a meaningful share of that translation rather than requiring a manual line-by-line rewrite.&lt;/p&gt;
&lt;h3 id="is-pulumi-a-drop-in-terraform-replacement"&gt;Is Pulumi a drop-in Terraform replacement?&lt;/h3&gt;
&lt;p&gt;It can be, if you want it to be. Pulumi Cloud now works as a &lt;a href="https://www.pulumi.com/docs/iac/get-started/terraform/terraform-state-backend/"&gt;Terraform state backend&lt;/a&gt;, so a team can point its existing Terraform or OpenTofu CLI at Pulumi Cloud and keep every &lt;code&gt;.tf&lt;/code&gt; file exactly as written — no rewrite required. Pulumi also &lt;a href="https://www.pulumi.com/docs/iac/languages-sdks/hcl/"&gt;supports HCL as a first-class language&lt;/a&gt; alongside Python, TypeScript, JavaScript, Go, .NET, Java, and YAML, so HCL modules can be authored and consumed natively inside Pulumi IaC.&lt;/p&gt;
&lt;p&gt;For teams that do want to move off HCL entirely, that&amp;rsquo;s also an option: Pulumi&amp;rsquo;s general-purpose languages let infrastructure code get loops, functions, tests, and packages that HCL doesn&amp;rsquo;t offer. Moving to that model means translating configuration into code rather than reusing files unchanged, though the underlying model carries over — state, providers, and resources map conceptually in similar ways — and Pulumi provides tooling to import existing Terraform-managed infrastructure and convert Terraform configuration into a starting Pulumi program, which teams typically use as a first draft rather than a finished migration.&lt;/p&gt;
&lt;h3 id="does-opentofu-support-everything-terraform-does"&gt;Does OpenTofu support everything Terraform does?&lt;/h3&gt;
&lt;p&gt;OpenTofu tracks Terraform&amp;rsquo;s last open-source release closely and remains compatible with most existing Terraform configurations and providers. Since forking, its Linux Foundation-governed maintainers have also shipped features Terraform hadn&amp;rsquo;t offered, such as state encryption, while newer HashiCorp-only Terraform features naturally won&amp;rsquo;t appear in OpenTofu unless the community implements equivalents independently.&lt;/p&gt;
&lt;h3 id="is-cdk-for-terraform-cdktf-still-supported"&gt;Is CDK for Terraform (CDKTF) still supported?&lt;/h3&gt;
&lt;p&gt;No. HashiCorp archived CDKTF on December 10, 2025 and says in its own FAQ that &amp;ldquo;no further updates, fixes, or improvements (including compatibility updates) will be made.&amp;rdquo; Existing CDKTF code keeps running since the license is MPL, but there&amp;rsquo;s no maintainer behind it. Teams still on CDKTF should move to native HCL on Terraform or OpenTofu, or to a maintained general-purpose-language platform like Pulumi. This is a different project from AWS CDK, which is unaffected and actively developed.&lt;/p&gt;
&lt;h3 id="is-terragrunt-a-terraform-alternative"&gt;Is Terragrunt a Terraform alternative?&lt;/h3&gt;
&lt;p&gt;Not really. Terragrunt is an orchestration wrapper that sits on top of Terraform or OpenTofu to keep large, multi-environment HCL codebases DRY; it doesn&amp;rsquo;t run infrastructure on its own, introduce a new provider model, or change what language you write in. See the &lt;a href="#terragrunt"&gt;Terragrunt section&lt;/a&gt; above for its 1.0 release and current defaults. Teams pick Terragrunt to make Terraform or OpenTofu easier to operate at scale, not as a replacement for either.&lt;/p&gt;
&lt;p&gt;For a broader roundup covering the full infrastructure-as-code category rather than Terraform alternatives specifically, see our guide to &lt;a href="https://www.pulumi.com/what-is/top-iac-tools/"&gt;the best IaC tools&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Terraform remains a capable, widely used tool, and for teams with no appetite to change, OpenTofu offers a nearly friction-free path to the same workflow under different governance. The question worth asking for 2026 is whether your infrastructure tooling can keep pace with how your engineering organization actually builds software now, with AI coding agents as active participants rather than autocomplete. That question favors platforms built on real, general-purpose languages, tested and reviewed the same way application code is, over any tool, new or established, still built around a purpose-specific configuration syntax. Evaluate honestly against your own cloud footprint, existing platform investment, and how central AI-assisted development already is to your team, and the right alternative, or the right reason to stay put, becomes clear quickly.&lt;/p&gt;</description><author>Pulumi Content Team</author><category>terraform</category><category>infrastructure-as-code</category><category>ai</category><category>platform-engineering</category><category>devops</category></item></channel></rss>