Pick a team that already ships software for a living: TypeScript in the editor, a package manager they trust, a test suite they run on every pull request, refactors driven by the type checker. Now ask them to describe their cloud infrastructure. The interesting question is not which tool is most powerful. They are all powerful enough. The question is which one feels like the work they already do, and which one feels like learning a second, smaller language to say things they already know how to say.
That is what "ease of use" actually means for infrastructure-as-code, and it is the lens this post uses to compare four tools: Pulumi, Terraform and its open-source fork OpenTofu (both authored in HCL), AWS CloudFormation (authored in YAML or JSON), and the AWS CDK. We use Pulumi at Omnihash, so read this as a balanced view from people who have a preference, not a pitch.
The language model is the whole story
Every other difference flows from one decision: what do you write your infrastructure in.
CloudFormation sits at the declarative end. You write YAML or JSON that describes the resources you want, and AWS reconciles reality to match. There is no real logic in the document. There are intrinsic functions and conditions bolted on, but they are a constrained mini-language living inside data, and complex YAML has a way of becoming write-only.
Terraform and OpenTofu use HCL, a domain-specific language built for exactly this job. HCL is more pleasant than raw YAML: it has variables, expressions, loops through count and for_each, and modules for reuse. But it is still a DSL with a ceiling. When you hit something HCL was not designed to express, you feel the wall, and the escape hatches (templating, external data sources) are awkward.
Pulumi and the AWS CDK take the other path: you author infrastructure in a real general-purpose language. Pulumi supports TypeScript, Python, Go, and C#. The CDK supports a similar set but compiles down to a CloudFormation template under the hood, which both grounds it and constrains it to AWS. In both, a resource is an object, a reusable pattern is a function or a class, and a conditional is just an if statement.
Figure 1. The infrastructure-as-code spectrum, from declarative YAML through a domain-specific language to a general-purpose programming language. Tools sit where their authoring model lives, not where their power ceiling is.
The comparison at a glance
| Dimension | Pulumi | Terraform / OpenTofu | CloudFormation | AWS CDK |
|---|---|---|---|---|
| Language | TypeScript, Python, Go, C# | HCL (a DSL) | YAML or JSON | TypeScript, Python, others |
| Reuse | Functions, classes, packages | Modules, for_each | Nested stacks, macros | Constructs, classes |
| Type safety | Yes, from the host language | Limited, HCL-level | None at authoring time | Yes, from the host language |
| IDE tooling | Full, language native | Good with the HCL plugin | Minimal | Full, language native |
| Testing | Unit and integration in your language | Plan checks, third-party tools | Very limited | Unit tests plus assertions |
| Learning curve | Low if you code, new concepts otherwise | Moderate, learn HCL | Low to start, hard at scale | Low if you code, AWS-shaped |
| Ecosystem | Growing, can wrap providers | Largest by far | AWS-only, mature | AWS-only, strong |
| Cloud scope | Multi-cloud | Multi-cloud | AWS only | AWS only |
Learning curve cuts both ways
If you write software, Pulumi and the CDK have the gentlest on-ramp, because most of what you already know transfers. You are not learning a language, you are learning a library. The new concepts are infrastructure ones: resources, state, outputs, and the fact that values like an IP address do not exist until after deployment.
That last point matters and is where the gentle on-ramp gets a bump. Pulumi outputs are asynchronous values, a bit like promises, and you cannot treat them as plain strings. Engineers new to Pulumi trip on this early. It is a real concept to learn, not a syntax to memorize.
For a team that does not write application code, the calculus flips. HCL and YAML ask you to learn less. There is no async value model, no package manager, no class hierarchy to reason about. A pure-operations team can read a Terraform module and know exactly what it does, because HCL deliberately cannot do very much. Constraint is a feature when the readers are not programmers.
Abstraction, reuse, and the freedom to over-engineer
This is where a real language pulls ahead, and also where it can hurt you.
In Pulumi or the CDK, a standard service (a load-balanced container, a queue with its consumer, a bucket with the right policies) becomes a class you instantiate with a few arguments. You get loops, conditionals, inheritance, and a real package manager: npm, PyPI, Go modules. You publish a component, version it, and import it like any other dependency. That is genuinely powerful for a team with many similar services.
Terraform and OpenTofu answer this with modules, and the module ecosystem is the single strongest reason to choose them. For a huge range of common needs, a battle-tested public module already exists. You compose modules with for_each and variables. It is less expressive than a class hierarchy, and that is sometimes exactly right.
The honest cost of a real language is that it lets you over-engineer. Infrastructure code written by an enthusiastic developer can grow factories, abstract base classes, and clever metaprogramming until nobody can tell what will actually be deployed. HCL's ceiling makes that failure mode much harder to reach. With Pulumi, the discipline has to come from you, the same discipline you already apply to application code: keep it boring, keep it readable, resist cleverness.
Type safety, IDE tooling, and testing
Here the general-purpose languages win cleanly, and it is not close.
Because Pulumi and the CDK run inside a real language, you get the entire toolchain for free. Autocomplete suggests valid resource properties. The type checker rejects a misconfigured resource before you ever run a deploy. Go-to-definition jumps into the provider. Refactoring across a large codebase is a tool action, not a careful find-and-replace. CloudFormation YAML offers almost none of this at authoring time, and HCL sits in between: good with the editor plugin, but without the depth of a full language server.
Testing follows the same shape. With Pulumi you can write unit tests in your language's normal framework, mocking the cloud and asserting that the right resources were declared with the right properties, all without touching AWS. The CDK has comparable assertion testing. Terraform leans on plan inspection and third-party tools, which work but feel external. CloudFormation testing is thin, mostly deploy-and-see. If your team values a fast test loop on infrastructure, the language-native tools are a real advantage.
State management, fairly
State is where the honest caveats live, and where Pulumi does not get to claim a clean win.
Terraform, OpenTofu, and Pulumi are all stateful: they keep a record of what they manage and reconcile against it. That means you must host that state somewhere durable and locked, an object store and a lock table, or a managed backend. Pulumi offers a hosted service for this, and a self-managed backend if you would rather own it, but either way state is a thing you operate. There is no escaping it.
CloudFormation and, by extension, the CDK are different here, and it is a genuine point in their favor: AWS manages the state for you. The stack lives in your account as a first-class AWS object. Nothing to host, nothing to lock, nothing to back up. For a team that wants the cloud provider to own that responsibility, this is a real simplification, and it is the kind of thing that is easy to undervalue until a corrupted state file ruins an afternoon.
So which is easiest
Ease of use is not one number, because the easiest tool depends on who is holding it.
- Pulumi is easiest for teams who already write software. The mental model is the one they use daily, the tooling is the tooling they already have, and the multi-cloud reach is a bonus. The cost is the async output model to learn and the discipline to not over-abstract.
- The AWS CDK is the natural pick for a software team that lives entirely on AWS and is happy to. You get the same language benefits, plus AWS-managed state, in exchange for being AWS-only.
- Terraform or OpenTofu is the safer default for pure-operations teams and large existing estates. The unmatched module ecosystem, the readable constraint of HCL, and the enormous community mean you are rarely the first to solve a problem. For many organizations that gravity is decisive, and choosing it is not a compromise.
- CloudFormation makes most sense when you want AWS and only AWS to own everything, state included, and your footprint is small enough that raw YAML stays manageable.
The fair summary is that Pulumi is the easiest of the four for people who think in code, and that this is a real and common situation rather than a universal truth. If your team already ships software, the tool that looks like software will feel easiest. If your team is operations-first, or you are standing on a mountain of existing HCL, the safer and often kinder choice is the one with the largest community and the smallest language to learn.
At Omnihash we reach for Pulumi most often because most of our teams are writing TypeScript, Node, and Python already, and the infrastructure feeling like the rest of the codebase is worth a lot. But the right answer is the one that fits the people maintaining it, and we are happy to think that through with you rather than hand you ours.