Pulumi lets you define infrastructure as code using general-purpose programming languages like Python, TypeScript, Go, and C#, while Terraform uses its own declarative configuration language called HashiCorp Configuration Language (HCL).
Let’s see Pulumi in action. Imagine you want to create an AWS S3 bucket.
import pulumi
import pulumi_aws as aws
# Create an AWS resource (S3 bucket)
bucket = aws.s3.Bucket("my-bucket",
acl="private",
tags={
"Environment": "Dev",
"ManagedBy": "Pulumi",
})
# Export the bucket name
pulumi.export("bucket_name", bucket.id)
This Python code defines an S3 bucket named "my-bucket" with private ACL and specific tags. pulumi.export makes the bucket’s ID (its name) visible after deployment. When you run pulumi up, Pulumi parses this code, determines the desired state of your infrastructure, and interacts with the AWS API to create or update resources.
Now, let’s look at the equivalent in Terraform using HCL:
resource "aws_s3_bucket" "my_bucket" {
bucket = "my-bucket"
acl = "private"
tags = {
Environment = "Dev"
ManagedBy = "Pulumi"
}
}
output "bucket_name" {
value = aws_s3_bucket.my_bucket.id
}
This HCL code achieves the same result. Terraform reads this configuration, plans the changes, and applies them to your AWS account.
The core problem both tools solve is managing infrastructure reliably and repeatably. Instead of manually clicking through a cloud provider’s console, you describe your desired infrastructure in code. This code can be version-controlled, tested, and reviewed, leading to more stable deployments.
Pulumi’s "real-language" approach means you leverage the full power of your chosen programming language. This includes features like loops, conditional logic, functions, classes, and existing libraries. For instance, creating multiple S3 buckets with slightly different configurations can be done with a simple for loop in Python, whereas HCL might require more verbose repetition or external data sources. You can also integrate infrastructure definition directly into your application code, sharing types and logic.
Terraform’s HCL, on the other hand, is designed specifically for infrastructure as code. It’s declarative, meaning you state what you want, not how to achieve it. This can make it easier to read for those less familiar with programming, and its extensive module ecosystem is a major strength. HCL has built-in constructs like for_each and count for creating multiple resources, and a rich set of functions for data manipulation.
The mental model for Pulumi involves thinking about your infrastructure as objects within a program. You instantiate resource classes, configure their properties, and manage dependencies implicitly through code structure or explicitly via pulumi.Output.apply. State management is handled by Pulumi’s service, which tracks your infrastructure’s current state.
For Terraform, the model is about defining resources and their relationships in a graph. HCL describes nodes (resources) and edges (dependencies). Terraform builds this graph, figures out the execution order, and applies changes. The terraform.tfstate file is crucial here, representing the current state of your managed infrastructure.
A key differentiator often overlooked is how Pulumi handles secrets. Because you’re using general-purpose languages, you can leverage standard language constructs and libraries for secret management, including integration with existing secret managers or even simple environment variables (though not recommended for production). Pulumi encrypts secrets in its state file automatically, abstracting away much of the complexity that can be a stumbling block for Terraform users when managing sensitive data directly within HCL or its state.
The next step in understanding these tools involves exploring their respective approaches to state management and how they handle drift detection.