<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>OpenTofu | John Nessime</title>
	<atom:link href="https://john-nessime.com/blog/tag/opentofu/feed/" rel="self" type="application/rss+xml" />
	<link>https://john-nessime.com/blog/tag/opentofu/</link>
	<description>Cloud, DevOps, Data &#38; AI — Built, Tested, Explained</description>
	<lastBuildDate>Thu, 30 Jul 2026 15:10:38 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0.2</generator>

<image>
	<url>https://john-nessime.com/blog/wp-content/uploads/2026/07/cropped-jn-32x32.png</url>
	<title>OpenTofu | John Nessime</title>
	<link>https://john-nessime.com/blog/tag/opentofu/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Pulumi vs Terraform: Choosing a Lane</title>
		<link>https://john-nessime.com/blog/devops/pulumi-vs-terraform/</link>
					<comments>https://john-nessime.com/blog/devops/pulumi-vs-terraform/#respond</comments>
		
		<dc:creator><![CDATA[John Nessime]]></dc:creator>
		<pubDate>Thu, 30 Jul 2026 15:10:36 +0000</pubDate>
				<category><![CDATA[Cloud Computing]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[Technical Guides]]></category>
		<category><![CDATA[Automation]]></category>
		<category><![CDATA[AWS]]></category>
		<category><![CDATA[CI/CD]]></category>
		<category><![CDATA[Cloud]]></category>
		<category><![CDATA[HCL]]></category>
		<category><![CDATA[Infrastructure]]></category>
		<category><![CDATA[Infrastructure as Code]]></category>
		<category><![CDATA[OpenTofu]]></category>
		<category><![CDATA[Production]]></category>
		<category><![CDATA[Pulumi]]></category>
		<category><![CDATA[Terraform]]></category>
		<guid isPermaLink="false">https://john-nessime.com/blog/?p=36</guid>

					<description><![CDATA[<p>The licence changed, the community forked, IBM bought HashiCorp, CDKTF was archived, and Pulumi started speaking HCL. Here is an honest read of where Pulumi vs Terraform actually stands in 2026, and how to pick a lane you will not regret.</p>
<p>The post <a href="https://john-nessime.com/blog/devops/pulumi-vs-terraform/">Pulumi vs Terraform: Choosing a Lane</a> appeared first on <a href="https://john-nessime.com/blog">John Nessime</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Every few months someone on a platform team reopens the same argument. Half the room wants infrastructure in TypeScript because they are tired of writing loops in a configuration language. The other half points out that the last person who tried that left behind two thousand lines nobody can review. Both sides are right, which is why the argument never resolves.</p>



<p class="wp-block-paragraph">The Pulumi vs Terraform question got harder in the last two years, not easier. The licence changed, the community forked, IBM bought HashiCorp, HashiCorp killed its own general-purpose-language option, and Pulumi started speaking HCL. A lot of the comparison articles you will find were written before any of that happened.</p>



<p class="wp-block-paragraph">This is an attempt at an honest read of where things actually stand, what the real trade-off is once you strip away the marketing, and how to pick a lane you will not regret in eighteen months.</p>



<h2 class="wp-block-heading">What actually changed</h2>



<p class="wp-block-paragraph">A short timeline, because the current landscape only makes sense against it.</p>



<ul class="wp-block-list">
<li><strong>August 2023</strong> — HashiCorp relicensed Terraform from MPL 2.0 to the Business Source License. Source-available, free for provisioning your own infrastructure, but you cannot build a competing commercial product on it.</li>

<li><strong>January 2024</strong> — OpenTofu shipped 1.6.0, a fork of the last MPL-licensed code, governed by the Linux Foundation.</li>

<li><strong>February 2025</strong> — IBM completed its acquisition of HashiCorp, valued at around $6.4 billion.</li>

<li><strong>December 2025</strong> — HashiCorp deprecated CDK for Terraform and archived the repository. The official way to write Terraform in a general-purpose language no longer exists.</li>

<li><strong>Also December 2025</strong> — Pulumi announced native HCL support and the ability to work with Terraform and OpenTofu state.</li>
</ul>



<p class="wp-block-paragraph">Those last two matter more than the licence drama, and they point in opposite directions. HashiCorp closed the door on writing Terraform in a real language. Pulumi opened the door to writing Pulumi in HCL. Whatever else you conclude, &#8220;HCL versus real languages&#8221; is no longer a clean line between two products.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">The real axis: a configuration language or a programming language</h2>



<p class="wp-block-paragraph">Strip out the licensing and the corporate ownership and one genuine difference remains. HCL is a purpose-built configuration language that describes desired state. Pulumi runs an actual program that constructs a resource graph, and then applies it.</p>



<p class="wp-block-paragraph">Here is the same trivial thing in both. Terraform:</p>



<pre class="wp-block-code"><code>resource "aws_s3_bucket" "logs" {
  for_each = toset(var.environments)
  bucket   = "acme-logs-${each.key}"

  tags = {
    Environment = each.key
    ManagedBy   = "terraform"
  }
}</code></pre>



<p class="wp-block-paragraph">And Pulumi, in TypeScript:</p>



<pre class="wp-block-code"><code>import * as aws from "@pulumi/aws";

const environments = ["dev", "staging", "prod"];

const buckets = environments.map(env =&gt;
  new aws.s3.Bucket(`logs-${env}`, {
    bucket: `acme-logs-${env}`,
    tags: {
      Environment: env,
      ManagedBy: "pulumi",
    },
  })
);</code></pre>



<p class="wp-block-paragraph">For something this small the difference is aesthetic. It stops being aesthetic around the third nested conditional.</p>



<h3 class="wp-block-heading">What you actually gain with a general-purpose language</h3>



<ul class="wp-block-list">
<li><strong>Real abstractions.</strong> Classes, functions, inheritance. A Pulumi component that encapsulates &#8220;our standard service&#8221; is just a class, and it composes the way code composes.</li>

<li><strong>Real testing.</strong> Jest, pytest, Go&#8217;s testing package. You can unit test infrastructure logic with the same tools and the same CI as the rest of your codebase.</li>

<li><strong>The package ecosystem.</strong> Need to parse a CSV of subnets, call an internal API, or do something genuinely awkward? It is an npm install rather than an external data source and a shell script.</li>

<li><strong>IDE support that works.</strong> Type checking, refactoring, jump-to-definition. HCL tooling has improved a lot but it is not the same thing.</li>
</ul>



<h3 class="wp-block-heading">What you lose, which comparison posts skip</h3>



<p class="wp-block-paragraph">This is the part worth reading twice, because it is where the regret comes from.</p>



<ul class="wp-block-list">
<li><strong>Constraint is a feature.</strong> HCL is limited on purpose. When a language cannot express clever things, nobody writes clever things. Give a team a real language and someone will eventually build a metaprogramming layer that generates resource names from a database lookup, and reviewing that pull request will be nobody&#8217;s idea of a good afternoon.</li>

<li><strong>Readability across skill levels.</strong> A network engineer who has never written TypeScript can usually read HCL and tell you what it provisions. The reverse is not true. If your infrastructure is touched by people who are not primarily programmers, that matters enormously.</li>

<li><strong>The hiring pool.</strong> There are far more engineers with Terraform on their CV than Pulumi. That is not an argument about quality, it is an argument about how long it takes to backfill a role.</li>

<li><strong>A dependency on a runtime.</strong> Pulumi programs need Node, Python, or Go present and correct in every environment that runs them, including CI. It is one more thing that can drift.</li>

<li><strong>Asynchrony leaks into the model.</strong> Resource outputs are not plain values. They are futures, and you have to handle them as such. Every Pulumi team hits this on day two.</li>
</ul>



<pre class="wp-block-code"><code># This does not do what a newcomer expects. bucket.id is an Output,
# not a string, so you get an object reference in the name.
# bucket_name = f"logs-for-{bucket.id}"

# Correct: work inside apply, where the value is resolved.
bucket_name = bucket.id.apply(lambda bid: f"logs-for-{bid}")</code></pre>



<p class="wp-block-paragraph">None of these are dealbreakers. They are just the bill that arrives later, and it is worth reading before you sign.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">State, providers, and the things that actually break</h2>



<h3 class="wp-block-heading">State management</h3>



<p class="wp-block-paragraph">Both tools keep a state file mapping your code to real resources, and both will ruin your afternoon if that file gets corrupted or out of sync.</p>



<p class="wp-block-paragraph">Terraform and OpenTofu use pluggable backends: S3 with DynamoDB locking, Azure Blob, GCS, Consul, or HCP Terraform. OpenTofu added client-side state encryption, which Terraform does not have natively and which is a genuine advantage if your compliance people care about what is sitting in that bucket.</p>



<p class="wp-block-paragraph">Pulumi defaults to Pulumi Cloud, which is free for individuals and paid for teams. It also supports self-managed backends, and you should know this because the default sometimes gets mistaken for a requirement:</p>



<pre class="wp-block-code"><code># Self-managed state in S3, no Pulumi Cloud account involved
pulumi login s3://my-pulumi-state-bucket

# Or entirely local, useful for experiments
pulumi login --local</code></pre>



<p class="wp-block-paragraph">Pulumi encrypts secrets in state by default. With Terraform, values marked sensitive are redacted in output but still sit in plaintext in the state file, which surprises people who assumed otherwise.</p>



<h3 class="wp-block-heading">The provider ecosystem, and a myth worth killing</h3>



<p class="wp-block-paragraph">You will read that Pulumi has a much smaller provider ecosystem. That was true once and is mostly no longer the point, because Pulumi bridges Terraform providers. If a Terraform provider exists, you can almost certainly use it from Pulumi, either pre-built in the Pulumi Registry or generated on demand.</p>



<p class="wp-block-paragraph">Two caveats that are real:</p>



<ul class="wp-block-list">
<li>Bridged providers can lag upstream releases. If you need a resource type that shipped last week, check before committing.</li>

<li>When something breaks deep in a bridged provider, you are debugging across a translation layer. That is a worse afternoon than debugging the provider directly.</li>
</ul>



<p class="wp-block-paragraph">Pulumi also ships native providers for Kubernetes and Azure, generated from the platform APIs, which give same-day coverage of new resources rather than waiting on a bridge.</p>



<h3 class="wp-block-heading">Policy and governance</h3>



<p class="wp-block-paragraph">Terraform&#8217;s first-class answer is Sentinel, which is tied to HCP Terraform. OpenTofu users generally reach for OPA or Conftest. Pulumi has CrossGuard, where policies are written in the same languages as everything else.</p>



<p class="wp-block-paragraph">If you are in a regulated environment and your auditors already know what Sentinel is, that is a heavier thumb on the scale than any language preference.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Licensing, since it is what forced the question</h2>



<ul class="wp-block-list">
<li><strong>Terraform</strong> — BSL 1.1. Source-available. Fine for provisioning your own infrastructure. Not fine for building a product that competes with HashiCorp. Each release converts to an open licence four years after publication, but only that release.</li>

<li><strong>OpenTofu</strong> — MPL 2.0, OSI-approved, Linux Foundation governance. No single vendor can relicense it.</li>

<li><strong>Pulumi</strong> — Apache 2.0 for the engine and CLI. Permissive, with a patent grant. Pulumi Corp monetises through Pulumi Cloud rather than through the licence.</li>
</ul>



<p class="wp-block-paragraph">For the overwhelming majority of teams, the BSL changes nothing about daily work. You are provisioning your own infrastructure and that is explicitly permitted. The concern is not today&#8217;s terms, it is that a single vendor demonstrated it can change them, and that vendor is now part of IBM.</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">If your objection to Terraform is licensing rather than language, the answer is OpenTofu, not Pulumi. Switching paradigms to solve a legal problem is an expensive way to do it.</p>
</blockquote>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Choosing a lane</h2>



<p class="wp-block-paragraph">Three coherent positions. Pick the one that matches your constraints rather than the one that sounds most modern.</p>



<h3 class="wp-block-heading">Lane one: OpenTofu</h3>



<p class="wp-block-paragraph">The default for most teams already running Terraform who are uneasy about the licence. Migration is genuinely close to free: same HCL, same providers, same state format, same commands.</p>



<pre class="wp-block-code"><code># Back up state first. Always.
terraform state pull &gt; backup-$(date +%F).tfstate

# Then initialise with tofu against the same backend
tofu init
tofu plan</code></pre>



<p class="wp-block-paragraph">A clean plan with no proposed changes means you are done. That is the whole migration for most codebases.</p>



<p class="wp-block-paragraph"><strong>Choose this if:</strong> you have existing Terraform, HCL is working for you, and you want open governance without retraining anyone.</p>



<h3 class="wp-block-heading">Lane two: Terraform with HCP</h3>



<p class="wp-block-paragraph">Still the biggest ecosystem, the most documentation, the most Stack Overflow answers, the most people who already know it. Stacks and ephemeral resources are Terraform-only. Sentinel is Terraform-only. If you are deep in HCP Terraform and it works, the licence is an abstract concern rather than a practical one.</p>



<p class="wp-block-paragraph"><strong>Choose this if:</strong> you are in a regulated industry, you need vendor support with a contract behind it, or your organisation is standardising on IBM and Red Hat tooling anyway.</p>



<h3 class="wp-block-heading">Lane three: Pulumi</h3>



<p class="wp-block-paragraph">The strongest case for Pulumi is not &#8220;real languages are nicer&#8221;. It is when your infrastructure genuinely needs logic that HCL fights you on: dynamic topologies, infrastructure derived from application code, platform teams building abstractions for other teams to consume. If you are writing a self-service platform rather than a set of environments, a real language stops being a preference and starts being the right tool.</p>



<p class="wp-block-paragraph"><strong>Choose this if:</strong> your team is primarily software engineers, your infrastructure has real branching logic, or you are building a platform product rather than provisioning a fixed estate.</p>



<p class="wp-block-paragraph"><strong>Do not choose it</strong> because writing loops in HCL annoyed you once.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">What migrating actually looks like</h2>



<p class="wp-block-paragraph">Terraform to OpenTofu is a swap. Terraform to Pulumi is a refactor, and anyone telling you otherwise is selling something.</p>



<p class="wp-block-paragraph">Pulumi does provide real tooling for it:</p>



<pre class="wp-block-code"><code># Convert HCL into a Pulumi program in your chosen language
pulumi convert --from terraform --language typescript

# Adopt the resources described by an existing state file
pulumi import --from terraform ./terraform.tfstate</code></pre>



<p class="wp-block-paragraph">Pulumi can also consume Terraform modules directly, so existing module investment is not automatically wasted. That is the single most useful fact for anyone considering a gradual move.</p>



<p class="wp-block-paragraph">A sane sequence:</p>



<ol class="wp-block-list">
<li>Pick one small, low-risk stack. Not the production VPC.</li>

<li>Convert it, import the state, and run a preview. The only acceptable result is no changes.</li>

<li>Refactor the generated code. Converted output is mechanically correct and stylistically awful, which is fine, but do not leave it that way.</li>

<li>Run both tools side by side for a while. Coexistence is supported; a big-bang cutover is not necessary.</li>

<li>Only then decide whether to move anything else.</li>
</ol>



<p class="wp-block-paragraph">If step two produces a diff you cannot explain, stop. An unexplained diff during an IaC migration is how resources get destroyed and recreated.</p>



<h2 class="wp-block-heading">Common mistakes</h2>



<ul class="wp-block-list">
<li>Switching tools to solve a licensing concern when OpenTofu solves it for free.</li>

<li>Picking the tool the loudest engineer prefers rather than the one the whole team can maintain.</li>

<li>Adopting CDKTF in 2026. It was archived in December 2025 and will not receive fixes.</li>

<li>Treating a converted program as finished code rather than a starting point.</li>

<li>Assuming Terraform state is safe because values are marked sensitive. Encrypt the backend.</li>

<li>Migrating production first because it is the stack that matters most. It is the stack that matters most.</li>

<li>Running two tools against the same resources without a clear ownership boundary.</li>
</ul>



<h2 class="wp-block-heading">Best practices whichever lane you pick</h2>



<ul class="wp-block-list">
<li>Remote state with locking and encryption, from the first commit. Never local state for anything shared.</li>

<li>Small stacks with clear boundaries. One enormous state file is the most common self-inflicted wound in this field.</li>

<li>Plan or preview output in every pull request, and require a human to read it.</li>

<li>Pin provider versions. Floating versions turn an unrelated deploy into an incident.</li>

<li>Policy as code before you need it, not after the audit.</li>

<li>Document the decision. Six months from now nobody will remember why, and the reasoning is worth more than the choice.</li>
</ul>



<h2 class="wp-block-heading">Frequently asked questions</h2>



<h3 class="wp-block-heading">Is Pulumi better than Terraform?</h3>



<p class="wp-block-paragraph">Neither is better in the abstract. Pulumi suits teams of software engineers building infrastructure with real logic in it. Terraform and OpenTofu suit teams provisioning a defined estate where constraint and readability matter more than expressiveness.</p>



<h3 class="wp-block-heading">Can I use Terraform providers with Pulumi?</h3>



<p class="wp-block-paragraph">Yes. Pulumi bridges Terraform providers, either pre-built in its registry or generated on demand, and can consume Terraform modules directly. Bridged providers can lag upstream releases, so verify coverage for anything recent.</p>



<h3 class="wp-block-heading">Should I move from Terraform to OpenTofu?</h3>



<p class="wp-block-paragraph">If licensing is your concern, it is close to free: same HCL, same providers, same state. Back up state, run <code>tofu init</code> and <code>tofu plan</code>, and confirm no changes. If you rely on Stacks, Sentinel, or HCP Terraform, staying put is reasonable.</p>



<h3 class="wp-block-heading">What happened to CDKTF?</h3>



<p class="wp-block-paragraph">HashiCorp deprecated it on 10 December 2025 and archived the repository. It gets no further fixes or compatibility updates. If you want general-purpose languages for infrastructure now, Pulumi is the maintained option.</p>



<h3 class="wp-block-heading">Does the BSL licence stop me using Terraform commercially?</h3>



<p class="wp-block-paragraph">Not for provisioning your own infrastructure, which is what almost everyone does. It restricts building a competing commercial IaC product on top of it. Check with your own legal team rather than a blog post if you are anywhere near that line.</p>



<h3 class="wp-block-heading">Can Pulumi and Terraform coexist?</h3>



<p class="wp-block-paragraph">Yes, and for any migration of size that is the sensible approach. Pulumi can reference Terraform state and consume Terraform modules. The rule is that each resource has exactly one owner.</p>



<h2 class="wp-block-heading">Wrapping up</h2>



<p class="wp-block-paragraph">The Pulumi vs Terraform decision is less about the tools than about who maintains your infrastructure and what it has to do. If your estate is a known set of environments maintained by people with mixed backgrounds, a constrained configuration language is a feature and OpenTofu gives you that without licensing anxiety. If you are building a platform, with branching logic and abstractions other teams consume, a real language earns its complexity and Pulumi is the maintained way to get one.</p>



<p class="wp-block-paragraph">What has genuinely changed is that the lanes are no longer walled off. Pulumi speaks HCL and reads Terraform state. OpenTofu is a drop-in for Terraform. Converting between them is tooling rather than a rewrite. That makes the decision less permanent than it feels, which is a good reason to pick deliberately and a bad reason to agonise.</p>



<p class="wp-block-paragraph">Pick the lane your team can still maintain when the person who chose it has moved on. Then write down why.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Need help choosing or migrating?</h2>



<p class="wp-block-paragraph">I work with teams on infrastructure as code decisions and the migrations that follow — Terraform to OpenTofu, Terraform to Pulumi, splitting up state files that grew too large, and wiring the whole thing into CI with policy checks that actually run.</p>



<p class="wp-block-paragraph">If you would rather talk it through with someone who has untangled a few of these, you can hire me on Upwork.</p>



<div class="wp-block-buttons is-layout-flex wp-block-buttons-is-layout-flex">
<div class="wp-block-button"><a class="wp-block-button__link wp-element-button" href="https://www.upwork.com/freelancers/~01f15a912ad84a6620" target="_blank" rel="noreferrer noopener">Work with me on Upwork</a></div>
</div>
<p>The post <a href="https://john-nessime.com/blog/devops/pulumi-vs-terraform/">Pulumi vs Terraform: Choosing a Lane</a> appeared first on <a href="https://john-nessime.com/blog">John Nessime</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://john-nessime.com/blog/devops/pulumi-vs-terraform/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
