Manually managing infrastructure is inefficient and challenging, particularly in larger organizations where complex configurations are applied at scale. Infrastructure as code (IaC) was designed to help solve this problem and has since diversified into many complementary and competing tools. Terraform and Pulumi are two of the most heavily represented IaC tools within the cloud today.
Infrastructure as code (IaC), sometimes called “programmable infrastructure,” refers to the management and configuration of infrastructure via code rather than through more manual or human-driven processes.
Pulumi
Pulumi is an IaC automation tool that partially depends on Terraform. Unlike Terraform, declarative configurations can be written using familiar programming languages rather than vendor-specific templating languages.
Pulumi also provides a visual console, allowing you to easily view configuration history, activities, resources, and stack settings.
Terraform
Terraform is a tool for safely and efficiently building, changing, and versioning infrastructure. Its goal is to enable developers to define cloud and on-prem resources in a human-readable format that can be version controlled and easily shared.
Terraform supports almost all cloud services available on the market and can manage and maintain IT resources and automate manual tasks.
Key differences
We will now compare Terraform and Pulumi across feature dimensions and look in detail at some of the most impactful differences. To do this, we’ll use a simple table, which you can find below.
Terraform | Pulumi | |
---|---|---|
Language support | Only supports HCL (Hashicorp Configuration Language) and JSON declarative languageJava, C# , Python, and other languages are technically supported via CDKHowever, it just translates the declarative code into JSON configuration | Supports general programming languages, like Typescript, Golang, and Python |
Testing and validation | No official test suite is providedYou need to rely on third-party libraries, such as Terratest and Kitchen-Terraform | You can choose and use the same language framework for unit testingFor integration testing, Pulumi only supports tests written in Go |
Infrastructure reusability and modularity | Resources can be placed into different directories to distinguish them and to allow for the reuse of code filesA resource’s definition can also be split into different files | Different stacks act as different environments, which limits resource sharing optionsWhen using an extension to map multiple stacks, the individual stack cannot be referenced directly to access its resources |
Importing code from other IaC tools | Not supported per se, the closest feature is the importing of existing infrastructure resources into state | Support for converting Kubernetes YAML and Azure Resource Manager (ARM), Terraform HCL templates to Pulumi |
State and secrets management | The state file is stored by default in the terraform.tfstate file on the local hard driveRemote state file storage relies on third-party cloud platforms | When using Pulumi SaaS, the state file is encrypted and stored onlineRequires registering a user with Pulumi |
Community | Large community with many adoptersOfficial documentation is pretty thorough | Growing, but still a comparatively smaller, communityDocumentation can be incomplete |
Embedding in application code | Terraform’s Go package can be used as a library file to add to an applicationAccording to the official information, the degree of application embedding into the code is still relatively low | Pulumi has a programmatic interface called Automation APIPulumi can therefore be fully embedded in your software project |
Deploy to the cloud | Terraform allows users to deploy resources to the cloud from on-premises devices | By default, Pulumi needs to deploy components to the cloud using its SaaS platform |
Providers | Multiple public clouds such as AWS, Google Cloud, etc. are supportedCan only use Terraform- compatible providers | Can additionally use Terraform providers as part of codeSome providers need to be repackaged using pulumi-tf-provider-boilerplate |
Language Support
Terraform
Terraform uses HCL (Hashicorp Configuration Language) and JSON as its two default configuration languages. HCL syntax consists of only a few basic elements:
- Top-level blocks
- Parameters
- Parameter values
With the introduction of CDK, Terraform now supports other programming languages such as Typescript and Python. Under the hood, though, it just converts this code into JSON, which Terraform consumes.
The following figure shows the basic HCL syntax structure.
resource "aws_vpc" "example" { cidr_block = "10.0.0.0/16" }
Here, resource is the top-level block, cidr_block is the parameter required by the resource, and “10.0.0.0/16” is the parameter value. In practice, this will create a VPC resource in AWS with a cidr_block of “10.0.0.0/16”.
Pulumi
Pulumi supports a multi-language runtime, and your choice of language does not affect which cloud provider can be targeted. Pulumi currently supports the following languages:
- Node.js – JavaScript, TypeScript, or any other Node.js compatible language
- Python – Python 3.6 or greater
- .NET Core – C#, F#, and Visual Basic on .NET Core 3.1 or greater
- Go – statically compiled Go binaries
If your favorite language isn’t listed, it may be on its way soon. Pulumi is open source, and it’s possible to add different languages.
Testing and Validation
Terraform
Writing unit tests or performing automated End-to-End (E2E) tests for Terraform requires a third-party solution. The most widely used one is Terratest, a Golang library with plenty of helper functions and testing patterns. You can use it to write unit, integration, and E2E tests.
Additionally, Terraform provides a built-in command, called terraform validate, for static code analysis and code linting. It’s essential to have at least code validation as part of your development flow, done manually or via CI/CD pipeline.
For example, a typo in a resource name can easily be missed by a human operator, but the ‘terraform validate’ step would immediately flag it (alongside bad code formatting and other issues). See our demonstration below.
resource "aws_vp" "example" { cidr_block = "10.0.0.0/16" } # terraform validate Error: Invalid resource type │ │ on iam.tf line 30, in resource "aws_vp" "example": │ 30: resource "aws_vp" "example" { │ │ The provider hashicorp/aws does not support resource type │ "aws_vp". Did you mean "aws_lb"?
Pulumi
As discussed, Pulumi uses general-purpose programming languages to define infrastructure. These programming languages have complete testing frameworks available, so testing Pulumi code is not that different from testing any other application code.
There are three types of automated tests that Pulumi supports:
- Unit tests, evaluating your code’s suggested behavior while isolating it from the live infrastructure (via mocking the calls)
- property tests, evaluating realistic outcomes of current infrastructure code by using either ephemeral (short-lived) environments, or more permanent ones
- integration tests, evaluating the behavior of code and its end state by testing all external entry-points (like public URLs)
Implementing every type of test may not be necessary or feasible, but Pulumi’s approach to testing makes it easier to combine them if needed.
Infrastructure reusability and modularity
Terraform
Terraform employs the concept of a module: an assembly of resources that can be reused multiple times. Practically speaking, a module is created by placing file(s) with Terraform code into a specific location.
The resource below creates an AWS VPC network with some basic configurations.
resource "aws_vpc" "example" { cidr_block = var.cidr_block enable_dns_support = var.enable_dns_support }
If we didn’t define a module, we would need to create several copies of the code for each VPC that had a different configuration.
Let’s define a module by placing the code above into the ‘modules/vpc’ folder of our IaC project. We can now use this to create two different VPC networks (for example, development and production) by calling the module and supplying it with input parameters:
module "dev_vpc" { source = "../modules/vpc" cidr_block = “10.1.0.0/24” enable_dns_support = false } module "prod_vpc" { source = "../modules/vpc" cidr_block = “10.10.0.0/24” enable_dns_support = true }
Pulumi
Pulumi allows you to share and reuse components using the programming language of your choice.
Below, Typescript code defines a class that inherits pulumi.ComponentResource and creates an Azure Storage account.
import * as pulumi from "@pulumi/pulumi"; import * as azure from "@pulumi/azure"; export class Example extends pulumi.ComponentResource { connectionString: pulumi.Output<string>; constructor(name: string, opts?: pulumi.ComponentResourceOptions) { super("example:azure:storage-account", name, {}, opts); const account = new azure.storage.Account("${name}", { enableHttpsTrafficOnly: true, resourceGroupName: "storageaccount_rg" }, { parent: this }); this.connectionString = account.primary connection string; } }
After we complete the class development, we can run the npm publish command to publish the code as an npm package, which can then be reused as follows:
import { Example } from "custom.package.npm" import * as azure from "@pulumi/azure" new Example("testStorageAccount", { resourcegroupName: "someResourceGroup", enableHttpsTrafficOnly: false })
Importing code from other IaC tools
Terraform
Terraform does not support the direct conversion or inclusion of code from other IaC tools. The closest alternative is Terraform’s ability to import pre-existing infrastructure resources. This feature allows you to import resources into Terraform state, but you still need to create HCL definitions for each resource. Not every resource can be imported, depending on the Terraform providers you use.
The example below shows an AWS EC2 instance running with the ID “i-qwerty123”. We want to make this controllable by Terraform code. To do so, we should create a code definition for it (with matching configuration) and then run the ‘terraform import’ command referencing the pre-created resource.
resource "aws_instance" "foo" { ami = "ami-005e54dee72cc1d00" # us-west-2 instance_type = "t2.micro" ... }
# terraform import aws_instance.foo i-qwerty123
This was a simple example, and engineers will typically face more complicated tasks. Still, it demonstrates the steps needed to onboard external resources into Terraform quite well.
Pulumi
Pulumi supports the conversion of resource definitions from different sources. For example, you can get Pulumi to convert and deploy applications based on Kubernetes manifest or Helm chart. Azure Resource Manager (ARM) templates and Terraform HCL templates are also supported, assisting engineers in migration between tools. It’s not exactly a simple process, but it does provide flexibility, and at present, it is not a feature matched by Terraform.
State and secrets management
Terraform
Terraform saves the state of its infrastructure locally, in a .tfstate file, each time a change operation is performed. The state is stored in plain text JSON format and contains the details of the resources created, their parameters, configurations, etc. An example of a state file is shown below.
"instances": [ { "index_key": 0, "schema_version": 1, "attributes": { "ami": "ami-061eb2b23f9f8839c", "associate_public_ip_address": true, "availability_zone": "ap-southeast-1b", "cpu_core_count": 1, "cpu_threads_per_core": 1, "id": "i-088137f4282ab6167", "instance_state": "running", "instance_type": "t2.micro" ... } }]
If we need to delete a resource, but the file is damaged, there is no way to delete the resource gracefully. Therefore, as a best practice, use a remote state. Not only does this help avoid state file corruption, but it also supports versioning or locking and isolating state files for different working environments.
However, when using remote state storage, you will inevitably encounter conflicts (when multiple engineers or processes attempt to obtain the state simultaneously, for example). The state locking mechanism will prevent the state file from corruption (by parallel writes) and deny one of the operators access.
Another thing to consider is that Terraform’s state file stores secrets in an unencrypted format. Although this presents a real security threat, some remote state backends, like S3, support encryption. An alternative solution is to use another Hashicorp tool, Vault. Vault can be used to store sensitive information, and you can find a tutorial here.
Pulumi
By default, Pulumi’s state file is saved into the official Pulumi SaaS backend, where it is encrypted at rest. The backend also supports other features, such as state visualization and change history.
Like Terraform, self-managed options such as Amazon S3 and Google Cloud Storage are available.
Another great feature of Pulumi is its ability to migrate state files between different backends, which comes in handy for migration projects. Terraform does not currently offer this feature.
Last but not least, secrets management is a feature that is native to Pulumi and can perform CRUD operations on secrets, convert values and outputs to sensitive, and more.
Recommendations
So, which is the better overall tool? There is no definitive answer, just as there’s never a one-size-fits-all solution to a problem. Every organization is different, just as every infrastructure and engineering team is different.
To help, though, here are a few things to consider when choosing between Terraform or Pulumi:
- Infrastructure size: Terraform deals with large projects better than Pulumi and has more options for environment segregation
- Cloud engineering team: Does your organization have a dedicated team that lacks general programming skills? Or do you have development teams that care for applications and infrastructure? The choice is straightforward then: use Terraform in the first case and Pulumi in the latter
- Language to use: Pulumi gives power to engineers familiar with programming languages and paradigms, while Terraform and its DSL have a lower barrier to entry
- Out-of-the-box features: On paper, Pulumi provides more by default, but features to support more advanced scenarios or more extensive infrastructure come at a premium
Featuring guest presenter Tracy Woo, Principal Analyst at Forrester Research
Conclusion
It is often said that Terraform is the best tool in the field of IaC. It is more widely adopted and has built a vast community around itself. It also has plenty of well-rounded features to meet the needs of almost any engineering task.
This does not mean that Pulumi comes in as a poor second. In many ways, it does a better job than Terraform. It’s more secure by default, has a feature-rich SaaS offering, and has excellent migration capability.
Unfortunately, there are no silver bullets to solve all problems (werewolf issues aside). When choosing an IaC tool, it is necessary to be pragmatic and make choices based on your organization’s infrastructure, growth aspirations, and engineering culture.
Related Blogs
The New FinOps Paradigm: Maximizing Cloud ROI
Featuring guest presenter Tracy Woo, Principal Analyst at Forrester Research In a world where 98% of enterprises are embracing FinOps,…
Why FinOps Faces an Existential Crisis—and What Can Save It
As a technology leader of twenty-five years, I have worked on many solutions across a variety of sectors. These solutions…