# Terraform vs Pulumi: Which to Use

> Originally published on [DevToolHub](https://devtoolhub.com/terraform-vs-pulumi/).

![HCL and Python programs each pass through a state file to the same cloud providers](https://devtoolhub.com/wp-content/uploads/2026/11/2026-11-10-terraform-vs-pulumi-diagram.webp)

Choose Terraform when your team wants one declarative language that every cloud engineer already reads. Choose Pulumi when you want loops, tests and secrets handled in a real programming language. The terraform vs pulumi decision matters less for what each tool can build, because both deploy to AWS, Azure and GCP. It matters more for how they store state and survive refactors.

I ran the same resources through both tools to see where they differ. I used OpenTofu 1.13.9 and Pulumi 3.274.1 with Python. Nothing touched a cloud account. I used the `random` provider, so every resource lives only in state.

## What Is the Difference Between Terraform vs Pulumi?

Terraform describes infrastructure in HCL, a configuration language built for this job. Pulumi lets you describe it in Python, TypeScript, JavaScript, Go, .NET or Java. Pulumi's own [comparison page](https://www.pulumi.com/docs/iac/concepts/vs/terraform/) lists those languages, plus YAML and HCL. It says Terraform "uses HCL exclusively."

| | Terraform / OpenTofu | Pulumi |
|---|---|---|
| Language | HCL | Python, TypeScript, Go, .NET, Java, YAML |
| Default state | Local file, or a remote backend you configure | Pulumi Cloud, with self-managed backends available |
| Secrets in state | Plain text unless you add encryption | Encrypted by default |
| Rename a resource safely | `moved` block | `aliases` option |
| Licence | Terraform CLI is BSL 1.1, OpenTofu is MPL 2.0 | Apache 2.0 |
| Tests | `terraform test`, Terratest | Your normal test framework |

The state and licence rows come from Pulumi's page, so they reflect Pulumi's view of Terraform. I checked the secrets row myself in the test below. For the Terraform fork question, see [OpenTofu vs Terraform](https://devtoolhub.com/opentofu-vs-terraform/).

## How Does the Same Config Look in Terraform vs Pulumi?

Both tools express a keyed loop, a generated secret and an output in about 15 lines. I built three `random_pet` names, one for each of `dev`, `staging` and `prod`. I also created one `random_password`. Here is the HCL:

```hcl
variable "envs" {
  type    = list(string)
  default = ["dev", "staging", "prod"]
}

resource "random_pet" "name" {
  for_each = toset(var.envs)
  length   = 2
  prefix   = each.key
}

resource "random_password" "db" {
  length = 16
}

output "names" {
  value = { for k, v in random_pet.name : k => v.id }
}
```

And the Pulumi program in Python:

```python
import pulumi
import pulumi_random as random

envs = ["dev", "staging", "prod"]

names = {e: random.RandomPet(f"name-{e}", length=2, prefix=e) for e in envs}
db = random.RandomPassword("db", length=16)

pulumi.export("names", {e: p.id for e, p in names.items()})
```

The preview output is where you feel the difference first. `tofu plan` reported `Plan: 4 to add, 0 to change, 0 to destroy.` and showed every attribute. `pulumi preview` printed one line per resource and reported `5 to create`. The fifth is the stack itself.

Changing the list to `["dev", "qa", "prod"]` gave the same result in both tools. One resource was created and one destroyed, and `dev` and `prod` stayed untouched. So keyed loops behave the same either way.

## Where Does Each Tool Keep Secrets in State?

OpenTofu wrote the generated password into `terraform.tfstate` as plain text. Pulumi stored the same value as ciphertext. This was the biggest practical difference in my test. After `tofu apply`, I opened the state file and found the `random_password` entry:

```json
"result": "cVq-B3MvW+QCGYhA",
"length": 16,
```

The file also listed `result` under `sensitive_attributes`. That flag only hides the value in CLI output. It does not encrypt anything. Pulumi's docs say the same about Terraform: "Sensitive values are not encrypted in the state file."

The Pulumi state for the same resource looked like this:

```json
"result": {
  "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270",
  "ciphertext": "v1:m+xLJWdYPNumRzyw:O6sab+Z3GWlcV4j8llyJu2PIfm8YPduK2psiVe6xVmC/YA=="
}
```

⚠️ Note: I ran Pulumi against a local file backend with an empty passphrase. That proves the field gets encrypted, not that the key is well protected. In real use, pick Pulumi Cloud or a KMS-backed secrets provider.

You can close the gap on the OpenTofu side. OpenTofu's [state encryption docs](https://opentofu.org/docs/language/state/encryption/) show a `terraform { encryption { ... } }` block with key providers such as PBKDF2, AWS KMS, GCP KMS and Azure Vault. I did not test it. The point is that encryption is opt-in there and automatic in Pulumi.

For more on what lives in a state file, read [Terraform state explained](https://devtoolhub.com/terraform-state-explained/).

## What Happens When You Rename a Resource?

A rename destroys and recreates the resource in both tools unless you tell them the old identity. I renamed `db` to `database` in each project. Without any help, OpenTofu planned `random_password.database will be created` and `random_password.db will be destroyed`. Pulumi showed a `create` and a `delete` for the same pair.

For a database or a bucket, that plan is how people lose data. Each tool has a fix, and both worked in my test. In OpenTofu you add a `moved` block, per the [Terraform docs](https://developer.hashicorp.com/terraform/language/moved):

```hcl
moved {
  from = random_password.db
  to   = random_password.database
}
```

The plan then read `random_password.db has moved to random_password.database` and `Plan: 0 to add, 0 to change, 0 to destroy.` In Pulumi you add an alias, as described on the [aliases page](https://www.pulumi.com/docs/iac/concepts/options/aliases/):

```python
db = random.RandomPassword(
    "database", length=16,
    opts=pulumi.ResourceOptions(aliases=[pulumi.Alias(name="db")]),
)
```

The rename disappeared from the preview, and only the `qa`/`staging` change from the earlier test remained. Pulumi's docs say you can remove the alias once every stack has migrated.

## Can You Convert Terraform to Pulumi?

Yes. `pulumi convert --from terraform --language python` reads a directory of `.tf` files and writes a Pulumi program. I ran it on the HCL above. It produced a working-looking `__main__.py`, and a `pulumi preview` of the result showed the same `5 to create`.

The output reads like a translation, not like code a person would write. It turned my `for_each` into a loop over `sorted(...)` items and wrapped the list in `config.get_object("envs")`. You will want to tidy it by hand.

One part failed on my machine. The converter tried to `pip install` its dependencies into my system Python 3.9 and hit a resolver conflict. The program was still generated, and I ran it in a virtualenv instead. Use `--generate-only` if you want to manage dependencies yourself.

For a first Pulumi project in Python, see [creating an AWS S3 bucket with Pulumi](https://devtoolhub.com/creating-an-aws-s3-bucket-with-pulumi-in-python/). The older [Pulumi overview](https://devtoolhub.com/pulumi-iac-what-it-is-and-how-it-differs-from-other-iac-tools/) covers the concepts.

## Terraform vs Pulumi: Which Should You Choose?

Pick the tool that matches how your team already works. The test results point to three clear rules:

- **Choose Terraform or OpenTofu** if most of your team writes infrastructure but not application code. HCL is small, and every cloud engineer you hire has probably read it. Pick OpenTofu if the BSL licence is a problem for your company.
- **Choose Pulumi** if you want real loops, functions and unit tests, or if your platform team already writes Python or TypeScript. Encrypted secrets by default is the other strong reason.
- **Stay where you are** if a working Terraform setup has no pain. Converting 200 modules costs more than any language benefit, and `moved` and `aliases` solve the same refactor problem.

Both support AWS, Azure and GCP through providers, so the cloud you use rarely decides this. The pace of the tools is similar. As of November 2026, the latest releases on GitHub were Terraform 1.17.2 (November 11), Pulumi 3.274.1 and OpenTofu 1.13.9 (both November 5).

## What I Did Not Test

I did not run Terraform itself. The HCL ran on OpenTofu 1.13.9, which forked from Terraform, so I expect the plan output to match. Treat the Terraform-side claims as coming from its docs. I did not test state encryption in OpenTofu, Pulumi Cloud, remote backends, or any real cloud provider. I also did not test `terraform test` or Pulumi's unit-test mocks. All three need an account or more setup than this guide covers.

## Frequently Asked Questions

**Q: Is Pulumi better than Terraform?**
A: Neither is better in general. Pulumi wins when you want a general-purpose language and encrypted state by default. Terraform wins when you want one small declarative language, a huge module ecosystem and the easiest hiring. In my test both handled loops and renames equally well.

**Q: Does Pulumi use Terraform providers?**
A: Often, yes. Pulumi's docs describe a Terraform bridge that adapts an existing Terraform provider into a Pulumi provider. In my test, the `random` provider ran as `pulumi-random` 4.21.6. Check that the provider you need has a Pulumi package before you commit.

**Q: Can Terraform encrypt its state file?**
A: Not by default. My OpenTofu run left a generated password in plain text in `terraform.tfstate`. OpenTofu has an opt-in `encryption` block for state and plan files. Pulumi's comparison page notes that HCP Terraform encrypts state at rest.

**Q: How do I migrate from Terraform to Pulumi?**
A: Run `pulumi convert --from terraform --language python` in your Terraform directory. It generates a Pulumi program from your `.tf` files, which you then review and test. It does not move your existing state, so plan the cutover per stack.

**Q: Is OpenTofu the same as Terraform?**
A: Close, but not identical. OpenTofu forked from Terraform 1.6.x under the MPL 2.0 licence and has since added features such as state encryption. My HCL ran unchanged, but check any version-specific syntax.

**Quick Summary:**
- Terraform uses HCL only, and Pulumi supports Python, TypeScript, Go, .NET and Java
- OpenTofu left a generated password in plain text in state, and Pulumi stored it encrypted
- A rename without help plans a destroy and create in both tools
- `moved` blocks in OpenTofu and `aliases` in Pulumi both turned the rename into zero changes
- `pulumi convert --from terraform` produced a runnable Python program from my HCL
- I tested OpenTofu 1.13.9 and Pulumi 3.274.1, not Terraform itself, and no real cloud resources

Before you settle terraform vs pulumi, take one real module and run it both ways. Rename a resource in it and check which tool warns you before it recreates anything.
