Terraform vs Pulumi vs CloudFormation: Best IaC Tool in 2026
Compare Terraform, Pulumi, and CloudFormation for Infrastructure as Code in 2026. Same S3 and EC2 infra in all three tools, honest comparison table, and a clear recommendation.
Get more content like this on Telegram!
Daily AI tips, notes & resources — free
I've used all three of these in production. My honest take: the tool wars around Terraform vs Pulumi vs CloudFormation generate way more heat than the differences actually deserve. But the differences are real, and picking the wrong one for your situation creates friction that compounds over time.
Let me show you the same infrastructure — an S3 bucket and an EC2 instance — in all three tools. Then we'll talk about what actually matters.
What Infrastructure as Code Solves
Before the tools, the problem. Clicking around in the AWS console to create infrastructure is fine for one-time experiments. It doesn't scale. You can't review changes before applying them, you can't reproduce the environment exactly, you can't track who changed what and when, and you can't recover quickly from disasters.
IaC solves all of this by treating infrastructure the same way you treat application code — version controlled, reviewed, tested, and automated. The 2023 HashiCorp State of Cloud Strategy Survey found that 86% of organisations were using or planning to use IaC, up from 70% in 2020.
Terraform
HashiCorp Terraform has been the dominant IaC tool since around 2017. It uses HCL (HashiCorp Configuration Language) — a declarative DSL designed specifically for infrastructure.
Creating an S3 bucket and EC2 instance in Terraform
main.tf:
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
backend "s3" {
bucket = "my-terraform-state"
key = "prod/main.tfstate"
region = "us-east-1"
}
}
provider "aws" {
region = var.aws_region
}
# S3 bucket
resource "aws_s3_bucket" "app_assets" {
bucket = "${var.project_name}-assets-${var.environment}"
tags = {
Environment = var.environment
Project = var.project_name
ManagedBy = "Terraform"
}
}
resource "aws_s3_bucket_versioning" "app_assets" {
bucket = aws_s3_bucket.app_assets.id
versioning_configuration {
status = "Enabled"
}
}
# EC2 instance
data "aws_ami" "amazon_linux" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["al2023-ami-*-x86_64"]
}
}
resource "aws_instance" "web" {
ami = data.aws_ami.amazon_linux.id
instance_type = var.instance_type
tags = {
Name = "${var.project_name}-web"
Environment = var.environment
}
}
variables.tf:
variable "aws_region" {
description = "AWS region"
type = string
default = "us-east-1"
}
variable "project_name" {
description = "Project name prefix for all resources"
type = string
}
variable "environment" {
description = "Deployment environment"
type = string
default = "production"
}
variable "instance_type" {
description = "EC2 instance type"
type = string
default = "t3.micro"
}
terraform.tfvars:
project_name = "myapp"
environment = "production"
instance_type = "t3.small"
Apply it:
terraform init # Downloads providers, sets up backend
terraform plan # Shows what will change (always review this)
terraform apply # Apply after confirming the plan
terraform destroy # Tear everything down
The terraform plan output is one of Terraform's best features — it shows exactly what will be created, modified, or destroyed before anything happens:
Plan: 3 to add, 0 to change, 0 to destroy.
+ aws_s3_bucket.app_assets
+ aws_s3_bucket_versioning.app_assets
+ aws_instance.web
What Terraform is good at
Multi-cloud support is Terraform's standout feature. The same tool and workflow manages AWS, GCP, Azure, Cloudflare, GitHub, Kubernetes, and hundreds more via providers. If your infrastructure spans providers, Terraform is hard to beat.
The state management system tracks what's deployed. When you run plan, Terraform compares desired state (your HCL) with actual state (the state file) and real infrastructure. This three-way reconciliation is what makes safe incremental changes possible.
Pulumi
Pulumi lets you write infrastructure using real programming languages — TypeScript, Python, Go, C#, Java, YAML. Your infrastructure code runs in a Pulumi runtime rather than being interpreted as a DSL.
Creating the same S3 bucket and EC2 instance in Pulumi (TypeScript)
pulumi new aws-typescript
index.ts:
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const config = new pulumi.Config();
const projectName = config.require("projectName");
const environment = config.get("environment") || "production";
const instanceType = config.get("instanceType") || "t3.micro";
// S3 bucket
const appBucket = new aws.s3.BucketV2(`${projectName}-assets`, {
tags: {
Environment: environment,
Project: projectName,
ManagedBy: "Pulumi",
},
});
const bucketVersioning = new aws.s3.BucketVersioningV2("app-assets-versioning", {
bucket: appBucket.id,
versioningConfiguration: {
status: "Enabled",
},
});
// EC2 instance
const amazonLinux = aws.ec2.getAmiOutput({
mostRecent: true,
owners: ["amazon"],
filters: [{
name: "name",
values: ["al2023-ami-*-x86_64"],
}],
});
const webServer = new aws.ec2.Instance("web-server", {
ami: amazonLinux.id,
instanceType: instanceType,
tags: {
Name: `${projectName}-web`,
Environment: environment,
},
});
// Export values for use elsewhere
export const bucketName = appBucket.bucket;
export const instanceId = webServer.id;
export const publicIp = webServer.publicIp;
pulumi stack init production
pulumi config set projectName myapp
pulumi up # Preview and deploy
pulumi destroy # Tear down
What makes Pulumi different
The real power shows up when your infrastructure has complex logic. In Terraform/HCL, loops, conditionals, and dynamic blocks are limited and often awkward. In Pulumi, you write regular TypeScript/Python:
// Create one S3 bucket per environment — easy in Pulumi, awkward in Terraform
const environments = ["dev", "staging", "production"];
const buckets = environments.map(env =>
new aws.s3.BucketV2(`app-${env}`, {
tags: { Environment: env }
})
);
// Conditional resource creation
if (environment === "production") {
new aws.cloudwatch.MetricAlarm("high-cpu-alarm", {
// ...
});
}
Pulumi's state can be stored in Pulumi Cloud (free tier available), S3, Azure Blob Storage, or locally. The default is Pulumi Cloud, which adds a management UI.
CloudFormation
AWS CloudFormation is AWS's native IaC service. Templates in YAML or JSON define resources, and CloudFormation manages the deployment lifecycle.
Creating the same infrastructure in CloudFormation
template.yaml:
AWSTemplateFormatVersion: '2010-09-09'
Description: 'S3 bucket and EC2 instance'
Parameters:
ProjectName:
Type: String
Description: Project name prefix for resources
Environment:
Type: String
Default: production
AllowedValues: [development, staging, production]
InstanceType:
Type: String
Default: t3.micro
AllowedValues: [t3.micro, t3.small, t3.medium]
Mappings:
AmiMap:
us-east-1:
AMI: ami-0c55b159cbfafe1f0
us-west-2:
AMI: ami-01e78c5619c5e68b4
Resources:
AppBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub '${ProjectName}-assets-${Environment}'
VersioningConfiguration:
Status: Enabled
Tags:
- Key: Environment
Value: !Ref Environment
- Key: Project
Value: !Ref ProjectName
- Key: ManagedBy
Value: CloudFormation
WebServer:
Type: AWS::EC2::Instance
Properties:
ImageId: !FindInMap [AmiMap, !Ref 'AWS::Region', AMI]
InstanceType: !Ref InstanceType
Tags:
- Key: Name
Value: !Sub '${ProjectName}-web'
- Key: Environment
Value: !Ref Environment
Outputs:
BucketName:
Value: !Ref AppBucket
Export:
Name: !Sub '${AWS::StackName}-BucketName'
InstanceId:
Value: !Ref WebServer
Export:
Name: !Sub '${AWS::StackName}-InstanceId'
Deploy it:
# Via AWS CLI
aws cloudformation deploy \
--template-file template.yaml \
--stack-name myapp-production \
--parameter-overrides ProjectName=myapp Environment=production
# Check stack status
aws cloudformation describe-stacks --stack-name myapp-production
# Delete stack and all resources
aws cloudformation delete-stack --stack-name myapp-production
What CloudFormation is good at
Native AWS integration is its strongest point. CloudFormation supports new AWS services on day one — sometimes before any third-party provider does. Rollback behaviour is built in: if a stack update fails, CloudFormation automatically reverts to the previous state, which is reassuring in production.
Drift detection is a feature I actually like: CloudFormation can detect when real AWS resources have drifted from what the template describes (someone manually changed something in the console).
The major limitation is AWS-only. If you ever add GCP or Azure resources, you need a second tool.
The Comparison Table
| Feature | Terraform | Pulumi | CloudFormation |
|---|---|---|---|
| Language | HCL (DSL) | Python, TypeScript, Go, C# | YAML / JSON |
| Multi-cloud | Yes (600+ providers) | Yes (multi-provider) | AWS only |
| State management | S3/remote backend (manual setup) | Pulumi Cloud / S3 / local | Managed by AWS |
| Vendor lock-in | Low | Low | High (AWS only) |
| Learning curve | Medium | Medium-High | Medium |
| Logic/loops | Limited (HCL) | Full language power | Very limited |
| Community size | Huge | Growing | Large (AWS-focused) |
| Rollback | Manual | Manual | Automatic |
| Cost | Free (OSS) / TF Cloud | Free tier / $50+/mo | Free (pay for resources) |
| Testing support | Terratest | Native unit tests | cfn-lint, TaskCat |
| Drift detection | Yes (terraform plan) | Yes (pulumi refresh) | Yes (built-in) |
| Module/reuse system | Terraform Registry | Pulumi Registry | Nested stacks |
My Honest Pick
Here's my straightforward take based on actual usage:
Use Terraform if you're multi-cloud, if your team is already familiar with HCL, if you want the largest community and most job market relevance, or if you need the widest provider support. Despite the license change, Terraform is still the default choice for most infrastructure teams in 2026. If you want open source, OpenTofu is a direct replacement.
Use Pulumi if your team consists of developers (not infrastructure specialists) who find HCL alien, if your infrastructure has complex logic that would be painful in HCL, or if you're building internal developer platforms where type safety matters. TypeScript + Pulumi is genuinely pleasant to work with. The learning curve is real, though — you need to understand both the cloud provider concepts and the Pulumi runtime model.
Use CloudFormation if you're AWS-only and want zero additional tooling, if you want native AWS support and automatic rollbacks, or if you're in a regulated environment where relying on a third-party tool adds compliance burden. It's also worth considering for small teams — no state bucket to manage, no extra credentials to configure.
For teams deploying to Kubernetes, these tools integrate with your CI/CD pipelines to provision the cluster before your application deploys. Terraform in particular pairs well with the deploy Node.js to Kubernetes workflow — Terraform creates the EKS cluster, your pipeline deploys the app.
The web dev roadmap 2026 covers where IaC fits in the broader infrastructure skill set if you're mapping out what to learn next.
Conclusion
Terraform is still the most versatile choice for most teams in 2026. The multi-cloud support and community are hard to beat. Pulumi is genuinely excellent for developer-led teams who want to use real languages. CloudFormation is the safe, low-friction pick for AWS-only shops.
Don't agonise over the decision. Pick one, learn it properly, and use it consistently. A team that's proficient in CloudFormation ships better infrastructure than a team that half-knows Terraform. The best IaC tool is the one your team actually uses and understands.
FAQ
Is Terraform still worth learning in 2026 despite the BSL license change? Yes. HashiCorp's 2023 license change from MPL to BSL mainly affects companies building competing products using Terraform's code. For engineers using Terraform to manage their own infrastructure, nothing changed in practice. OpenTofu (the open-source fork) is also maturing fast if you prefer a fully open alternative. Terraform's community, provider ecosystem, and job market demand are still dominant.
Can I use Pulumi with existing Terraform state? Yes. Pulumi has a Terraform state migration tool and can import existing resources managed by Terraform. The process isn't always clean — complex modules can require manual adjustment — but it works for most common resources. You can also run both tools side by side during a migration period, which is usually safer than a big-bang switch.
What happens to CloudFormation stacks if I stop paying for AWS? CloudFormation stacks themselves don't cost money — it's the resources inside them (EC2 instances, RDS databases, etc.) that generate charges. If you stop paying AWS, your resources get suspended and eventually terminated, but the CloudFormation stack definitions (YAML/JSON templates) remain in your account until you delete them. Your infrastructure templates are always yours; you're just paying for the running resources.
Frequently Asked Questions
AiTechWorlds Team
✓ Verified WriterThe AiTechWorlds team is passionate about AI, technology, and education. We create high-quality, research-backed content to help you learn, grow, and succeed in the modern digital world.
Related Articles
AWS vs Azure vs GCP for Startups: Pricing and Free Tier Guide 2026
AWS, Azure, or GCP for your startup in 2026? Real free tier limits, monthly cost estimates, and honest recommendations based on your actual use case.
How to Use Docker Compose for Local Dev (Node.js + PostgreSQL)
Set up a full local dev environment with Docker Compose, Node.js, PostgreSQL, and pgAdmin. Includes .env config, named volumes, healthchecks, and common error fixes.
5 GraphQL Resolver Best Practices (DataLoader, Error Handling)
Write efficient GraphQL resolvers that don't hammer your database. DataLoader N+1 fix, error handling patterns, auth in context, and resolver performance comparison.
7 Logging Strategies for Microservices (ELK, Loki, Fluentd)
Centralized logging for microservices: compare ELK, Loki, Fluentd, and Datadog with real configs, cost breakdown, and 7 battle-tested strategies.