Infrastructure as Code: Terraform vs. Pulumi and When Each Makes Sense

Infrastructure as Code: Terraform vs. Pulumi and When Each Makes Sense

Infrastructure as Code is no longer a differentiator—it’s table stakes. The question isn’t whether to use IaC, but which tool, and how to use it in a way that doesn’t produce a codebase more unmaintainable than the hand-configured infrastructure it replaced.

The Core Comparison

Terraform uses HCL (HashiCorp Configuration Language), a purpose-built declarative language. You describe the desired state; Terraform figures out what API calls to make.

Pulumi uses general-purpose programming languages—TypeScript, Python, Go, Java, C#. You write programs that describe infrastructure. The full power of your language (loops, conditionals, functions, testing frameworks) is available.

Both produce an execution plan showing what will be created/modified/destroyed, both maintain state, and both have extensive provider ecosystems.

Where Terraform Excels

Simplicity of mental model: HCL is declarative. You read a .tf file and see exactly what infrastructure it produces. There’s no control flow to trace, no dynamic dispatch, no abstractions to unwind.

resource "aws_instance" "web" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t3.medium"

  tags = {
    Name        = "web-server"
    Environment = "production"
    ManagedBy   = "terraform"
  }
}

resource "aws_security_group" "web" {
  name = "web-sg"

  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

Ecosystem maturity: The Terraform provider ecosystem is enormous. If a cloud service exists, there’s almost certainly a Terraform provider for it.

Community knowledge: Terraform modules, patterns, and examples are widely available. Stack Overflow is full of Terraform questions and answers. New engineers can be productive quickly.

OpenTofu: HashiCorp changed Terraform’s license from MPL to BSL in 2023. OpenTofu is the community fork maintaining the MPL license. For most users, OpenTofu is a drop-in replacement and growing fast.

Where Pulumi Excels

Real languages for real complexity: When your infrastructure has conditional logic, loops over external data sources, or complex dependency graphs, HCL becomes painful.

// Pulumi TypeScript
import * as aws from "@pulumi/aws";

const zones = await aws.getAvailabilityZones({ state: "available" });

// Create a subnet in each AZ - natural with TypeScript
const subnets = zones.names.map((az, idx) =>
  new aws.ec2.Subnet(`subnet-${az}`, {
    vpcId: vpc.id,
    cidrBlock: `10.0.${idx}.0/24`,
    availabilityZone: az,
    tags: { Name: `subnet-${az}` }
  })
);

In Terraform, this pattern requires count or for_each, which work but are less intuitive and have limitations.

Testing: Pulumi programs are regular programs and can be unit tested:

// Pulumi unit test (TypeScript)
describe("Infrastructure tests", () => {
  it("should use required tags", async () => {
    const infra = await import("./infra");

    const instance = infra.webInstance;
    const tags = await new Promise(resolve =>
      instance.tags.apply(resolve)
    );

    expect(tags["Environment"]).toBeTruthy();
    expect(tags["ManagedBy"]).toBe("pulumi");
  });
});

Type safety: In TypeScript Pulumi programs, your IDE knows the shape of every resource. Mistyping a property name is a compile-time error, not a runtime failure after a terraform apply.

State Management: The Critical Shared Concern

Both Terraform and Pulumi maintain state—a record of what infrastructure exists. State is what allows them to compute a diff between desired and current state.

For any real usage, state must be stored remotely and locked to prevent concurrent modifications:

Terraform state in S3 (with DynamoDB locking):

terraform {
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "production/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "terraform-state-lock"
  }
}

OpenTofu with Terraform Cloud or Spacelift for managed state.

Pulumi with Pulumi Cloud (free tier available) or self-hosted with S3/Azure Blob.

Never store state locally. Never store state in Git (it contains secrets). Use a proper remote backend with locking.

Module/Component Patterns

Both tools support reusable abstractions.

Terraform modules:

module "vpc" {
  source = "terraform-aws-modules/vpc/aws"
  version = "5.1.0"

  name = "my-vpc"
  cidr = "10.0.0.0/16"

  azs             = ["us-east-1a", "us-east-1b", "us-east-1c"]
  private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
  public_subnets  = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]
}

Pulumi component resources:

class ApplicationStack extends pulumi.ComponentResource {
  public readonly url: pulumi.Output<string>;

  constructor(name: string, args: AppArgs, opts?: pulumi.ResourceOptions) {
    super("company:app:ApplicationStack", name, {}, opts);

    const lb = new aws.lb.LoadBalancer(`${name}-lb`, {
      internal: false,
      loadBalancerType: "application",
    }, { parent: this });

    this.url = lb.dnsName;
    this.registerOutputs({ url: this.url });
  }
}

Pulumi components are more ergonomic for complex abstractions. Terraform modules work fine for most cases.

Drift Detection and Policy

Both tools can detect drift—differences between state and actual infrastructure:

# Terraform
terraform plan -detailed-exitcode  # Exit 2 if changes detected

# Pulumi
pulumi preview --diff

For policy enforcement, Sentinel (Terraform Cloud), Checkov, or OPA can enforce rules about what infrastructure is allowed to be created.

The Real Answer

If you’re starting fresh and your team knows TypeScript or Python: Pulumi. The language ergonomics, testing capability, and type safety compound over time.

If you have existing Terraform and a team comfortable with HCL: keep using Terraform (or OpenTofu). The switching cost isn’t worth it unless HCL is actively causing pain.

If you’re in a regulated environment: Terraform/OpenTofu has more compliance tooling, more third-party auditing, and more established patterns for FedRAMP/SOC2/etc.

Either way: remote state, code review for all changes, pipeline-applied (not laptop-applied), and drift detection. The discipline matters more than the tool.

Share this post: LinkedIn Reddit WhatsApp Mastodon
Jesse Borden

Jesse Borden

Software Engineer with an interest in hands on learning

I have several years of professional Information Technology (IT) experience leading staff and projects within the Department of War (DOW). I have managed Service Desk, Web Application Development, and System Administration teams. My two greatest passions are learning and conti...