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.

Hybrid Cloud Solutions Demo

See the best multi-cloud management solution on the market, and when you book & attend your CloudBolt demo we’ll send you a $100 Amazon Gift Card.

Book demo

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 language
  • Java, C# , Python, and other languages are technically supported via CDK
  • However, 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 provided
  • You need to rely on third-party libraries, such as Terratest and Kitchen-Terraform
  • You can choose and use the same language framework for unit testing
  • For 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 files
  • A resource’s definition can also be split into different files
  • Different stacks act as different environments, which limits resource sharing options
  • When 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 drive
  • Remote state file storage relies on third-party cloud platforms
  • When using Pulumi SaaS, the state file is encrypted and stored online
  • Requires registering a user with Pulumi 
Community
  • Large community with many adopters
  • Official documentation is pretty thorough 
  • Growing, but still a comparatively smaller, community
  • Documentation can be incomplete
Embedding in application code
  • Terraform’s Go package can be used as a library file to add to an application
  • According to the official information, the degree of application embedding into the code is still relatively low
  • Pulumi has a programmatic interface called Automation API
  • Pulumi 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 supported
  • Can only use Terraform- compatible providers

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”. 

Terraform + CloudBolt = Integrated enterprise workflows
Platform
Infra as Code (IaC)
Multi Cloud Support
Self-Service User Interface
Provisioning Approval Process
Cost Control
Integrations Like ServiceNow and Ansible
Terraform
Terraform + CloudBolt
Don’t let detractors impede enterprise-wide Terraform adoption

Learn More

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. 

“CloudBolt allows us to move faster with Terraform than previously with Terraform alone”

Head of Cloud Engineering & Infrastructure
Global Entertainment Company

Watch 2 minute Video

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
Terraform + CloudBolt = Integrated enterprise workflows

Allow less technical users launch your Terraform scripts from a user interface

Let managers approve provisioning via workflows and 3rd-party integrations

Don’t allow the lack of cost reporting get in the way of Terraform’s adoption

Don’t let detractors impede enterprise-wide Terraform adoption

Learn More

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.