Verified by Garnet Grid

Terraform vs Pulumi vs CloudFormation: IaC Decision Guide

Compare infrastructure-as-code tools for enterprise cloud deployments. Covers language support, state management, multi-cloud capabilities, and organizational fit.

Infrastructure-as-Code (IaC) eliminates manual cloud provisioning. But which tool? Terraform dominates with 80%+ market share, Pulumi is the developer favorite, and CloudFormation is the AWS-native choice. Each tool makes different trade-offs, and the wrong choice creates years of technical debt — state migration between IaC tools is painful and risky.

This guide helps you pick and stick, with the depth needed to defend your decision to leadership and your engineering team.


Quick Comparison

FeatureTerraformPulumiCloudFormation
LanguageHCL (custom DSL)Python, TypeScript, Go, C#, JavaJSON / YAML
Multi-cloud✅ Primary strength (4,000+ providers)✅ Uses TF providers + native❌ AWS only
State managementRemote backend (S3, GCS, TF Cloud)Pulumi Cloud or self-managedAWS-managed (automatic)
Learning curveMedium (HCL is not hard, but it’s unique)Low (if you know Python/TS)Low (for AWS users)
Ecosystem4,000+ providers, massive module registryTF providers + native providersAWS services only
Drift detectionterraform plan (manual)pulumi preview (manual)Drift detection (limited, newer feature)
TestingTerratest (Go), terraform validateNative unit tests in your languagecfn-lint, TaskCat
CostFree (open source) + TF Cloud ($$$)Free (open source) + Pulumi Cloud ($$)Free (AWS managed)
LicensingBSL (since Aug 2023) — not fully open sourceApache 2.0 (truly open source)Proprietary (AWS)
OpenTofuCommunity fork (pre-BSL, fully open source)N/AN/A

Terraform

The industry default. Battle-tested at enormous scale by companies with thousands of engineers.

# main.tf — Deploy an Azure Kubernetes cluster
terraform {
  required_providers {
    azurerm = { source = "hashicorp/azurerm", version = "~> 3.0" }
  }
  backend "azurerm" {
    resource_group_name  = "tfstate"
    storage_account_name = "garnetgridtfstate"
    container_name       = "state"
    key                  = "prod.terraform.tfstate"
  }
}

resource "azurerm_kubernetes_cluster" "main" {
  name                = "garnet-aks-prod"
  location            = var.location
  resource_group_name = azurerm_resource_group.main.name
  dns_prefix          = "garnet-prod"
  kubernetes_version  = "1.29"

  default_node_pool {
    name       = "system"
    node_count = 3
    vm_size    = "Standard_D4s_v5"
  }

  identity { type = "SystemAssigned" }

  network_profile {
    network_plugin = "azure"
    network_policy = "calico"
  }
}

output "kube_config" {
  value     = azurerm_kubernetes_cluster.main.kube_config_raw
  sensitive = true
}

Strengths:

  • Massive ecosystem — if a cloud service exists, a Terraform provider probably exists for it
  • Battle-tested at Facebook, Google, Stripe, and thousands of enterprises
  • Plan/apply workflow gives clear previews of changes before execution
  • Module registry with thousands of reusable components
  • Largest talent pool — easiest to hire for

Weaknesses:

  • HCL is a custom language — requires learning for every developer
  • Limited expressiveness — loops, conditionals, and dynamic blocks are awkward compared to real programming languages
  • State management complexity — state locking, remote backends, state corruption recovery
  • BSL licensing (August 2023) — no longer fully open source, which prompted the OpenTofu fork
  • No native unit testing framework — relies on external tools like Terratest

The OpenTofu Question

In August 2023, HashiCorp changed Terraform’s license from MPL (open source) to BSL (source-available, not open source). The Linux Foundation forked Terraform as OpenTofu. For new projects, evaluate OpenTofu if open-source licensing matters to your organization. For existing Terraform deployments, migration is straightforward (OpenTofu is API-compatible) but not urgent.


Pulumi

Write infrastructure in real programming languages with full IDE support, testing frameworks, and package managers.

# __main__.py — Same AKS cluster in Python
import pulumi
import pulumi_azure_native as azure

cluster = azure.containerservice.ManagedCluster(
    "garnet-aks-prod",
    resource_group_name=rg.name,
    location=rg.location,
    kubernetes_version="1.29",
    dns_prefix="garnet-prod",
    agent_pool_profiles=[{
        "name": "system",
        "count": 3,
        "vm_size": "Standard_D4s_v5",
        "mode": "System",
    }],
    identity={"type": "SystemAssigned"},
    network_profile={
        "network_plugin": "azure",
        "network_policy": "calico",
    },
)

pulumi.export("kubeconfig", cluster.kube_config_raw)

Strengths:

  • Real programming languages (Python, TypeScript, Go, C#, Java) — use loops, classes, conditionals, and abstractions naturally
  • Excellent developer experience — full IDE support, autocomplete, type checking
  • Native testing — write unit tests for infrastructure in pytest, Jest, or Go testing
  • Apache 2.0 license — truly open source, no BSL concerns
  • Can consume Terraform providers — access the full Terraform ecosystem

Weaknesses:

  • Smaller community and ecosystem than Terraform
  • Newer — less battle-tested at large enterprise scale
  • More rope to hang yourself — general-purpose languages enable over-engineering
  • Pulumi Cloud is the path of least resistance for state management (vendor dependency)
  • Fewer hiring candidates with Pulumi experience

CloudFormation

AWS-native, zero additional tooling or state management required.

# template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Resources:
  EKSCluster:
    Type: AWS::EKS::Cluster
    Properties:
      Name: garnet-eks-prod
      Version: '1.29'
      RoleArn: !GetAtt EKSRole.Arn
      ResourcesVpcConfig:
        SubnetIds: !Ref SubnetIds
        SecurityGroupIds:
          - !Ref ClusterSecurityGroup

  NodeGroup:
    Type: AWS::EKS::Nodegroup
    Properties:
      ClusterName: !Ref EKSCluster
      NodeRole: !GetAtt NodeRole.Arn
      ScalingConfig:
        DesiredSize: 3
        MinSize: 1
        MaxSize: 10
      InstanceTypes: [m6i.xlarge]

Strengths:

  • Deep AWS integration — first-class support for all AWS services, often on launch day
  • Free — no licensing costs, no state management costs
  • AWS-managed state — no S3 buckets, no locking tables, no corruption risk
  • StackSets — deploy across multiple accounts and regions in one operation
  • Drift detection built-in (newer feature)

Weaknesses:

  • AWS only — cannot manage GCP, Azure, or third-party services
  • Verbose YAML/JSON — complex stacks become thousands of lines, difficult to read
  • Slow deployments — CloudFormation stack operations are notoriously slow
  • Limited programming constructs — no real loops or conditionals (AWS CDK addresses this)
  • Error messages are often cryptic and debugging is painful

AWS CDK: The CloudFormation Upgrade

AWS CDK (Cloud Development Kit) lets you write CloudFormation in real programming languages (TypeScript, Python, Java, C#) and synthesizes to CloudFormation templates. It combines CloudFormation’s strengths (AWS integration, managed state) with real language support. Consider CDK if you are committed to AWS.


Decision Framework

Multi-cloud or non-AWS cloud?
├── Yes → Terraform (industry standard) or Pulumi (developer preference)
└── No (AWS only)?
    ├── Team prefers YAML / minimal tooling → CloudFormation
    ├── Team wants real programming language → CDK or Pulumi
    └── Team already uses Terraform → Keep using Terraform

Team size > 50 and needs standardization?
└── Terraform (largest talent pool, most hiring candidates)

Small team, fast iteration, TypeScript/Python shop?
└── Pulumi (best developer experience)

Deep OSS concerns about HashiCorp BSL?
└── OpenTofu (compatible, truly open source)

Migration Considerations

FromToDifficultyNotes
CloudFormation → TerraformMediumImport existing resources + write HCL
Terraform → PulumiLowpulumi convert can auto-convert HCL
Terraform → OpenTofuVery LowDrop-in replacement (API compatible)
Pulumi → TerraformMediumManual rewrite
Any → CloudFormationHardMust be AWS-only, manual rewrite

State Management Best Practices

PracticeTerraformPulumiCloudFormation
Remote stateS3 + DynamoDB lockPulumi Cloud or S3AWS-managed
State encryptionSSE-S3 or SSE-KMSManaged by Pulumi CloudAWS-managed
State backupS3 versioningBuilt-inAWS-managed
State lockingDynamoDB tableManagedAWS-managed
Split stateOne per environment/componentPulumi stacksNested stacks

Common IaC Anti-Patterns

  • Manual changes after deployment — Every manual console change creates drift. Use terraform plan or pulumi preview to detect it.
  • Monolithic state files — One state file for 500 resources means every plan takes minutes and blast radius is huge. Split into modules/stacks per domain (networking, compute, data).
  • Hardcoded values — Environment-specific values like VPC IDs, account numbers, and regions should come from variables or config files, never literals.
  • No state locking — Two engineers running terraform apply simultaneously can corrupt state. Use S3+DynamoDB (Terraform) or Pulumi Cloud for locking.
  • Ignoring plan output — Always review the plan before applying. Automated pipelines should require manual approval for production changes.

Migration Decision Guide

Current StateRecommended PathEffort
ClickOps (manual console)Start with Terraform (more learning resources)Medium
CloudFormationMigrate to Terraform (import existing resources)Medium
Terraform HCL (happy with it)Stay — adopt modules and workspacesLow
Terraform HCL (want type safety)Evaluate Pulumi or CDK for TerraformMedium
Custom scripts (bash/Python)Migrate to Terraform or PulumiHigh

Checklist

  • IaC tool selected based on cloud strategy, team skills, and licensing requirements
  • Remote state backend configured with encryption and locking
  • Module/component library created for common patterns (VPCs, K8s clusters, databases)
  • CI/CD pipeline runs plan/preview on every pull request
  • Drift detection scheduled (weekly minimum)
  • Secrets handled via external vault (not in state files or variables)
  • Code review required for all infrastructure changes
  • Blast radius limited by splitting state per environment and component
  • Rollback procedure documented and tested
  • Cost estimation integrated into PR workflow (Infracost, Pulumi cost estimates)

:::note[Source] This guide is derived from operational intelligence at Garnet Grid Consulting. For cloud infrastructure consulting, visit garnetgrid.com. :::

Jakub Dimitri Rezayev
Jakub Dimitri Rezayev
Founder & Chief Architect • Garnet Grid Consulting

Jakub holds an M.S. in Customer Intelligence & Analytics and a B.S. in Finance & Computer Science from Pace University. With deep expertise spanning D365 F&O, Azure, Power BI, and AI/ML systems, he architects enterprise solutions that bridge legacy systems and modern technology — and has led multi-million dollar ERP implementations for Fortune 500 supply chains.

View Full Profile →