Pulumi vs Terraform: An In-Depth Comparison
Tags: pulumi, terraform, infrastructureascode, devops
Published: 2024-04-15 12:14:43

The world of [Infrastructure-as-Code (IaC)](https://www.env0.com/blog/infrastructure-as-code-101) has evolved by leaps and bounds. With a growing number of IaC tools available, DevOps teams often find themselves weighing the pros and cons of popular choices that pits Pulumi vs Terraform and others.

In this blog, I’ll take a deep dive into the features, similarities, differences, and real-world use cases of [Terraform](https://www.env0.com/blog/terraform-tutorial) and [Pulumi](https://www.env0.com/blog/what-is-pulumi-and-how-to-use-it-with-env0). Specifically, I’m going to focus on the following:

*   **Comparison of Pulumi and Terraform:** I’ll cover features, language flexibility, and community support.
*   **Languages**: Terraform utilizes its own HCL while Pulumi supports multiple common programming languages. I will review how these differences in approach to state management can make one more suitable than the other.
*   **Real-world examples**: I’ll demonstrate how both tools can be used for managing cloud infrastructure across multiple platforms securely.

So, buckle up as we explore the fascinating world of Pulumi and Terraform.  
  
**Video Walkthrough**

{% embed https://vimeo.com/875583090 %}

**My Setup**

1.  **A GitHub account:** I’ll use GitHub Codespaces with all necessary tools installed for you.
2.  **Repository:** TL;DR: You can find [the main repo here.](https://github.com/samgabrail/pulumi-vs-terraform)

**Understanding Infrastructure-as-Code (IaC)**
----------------------------------------------

For posterity, remember that Infrastructure-as-Code provisions infra resources using preset configuration files instead of manually. 

The beauty of IaC lies in its ability to treat infrastructure like code, allowing us to manage it with the same processes, tools, and programming languages as application code. 

This enables us to leverage software development practices like version control, testing, and automation throughout the infrastructure provisioning lifecycle, making it easier to allocate infrastructure resources efficiently.

**Pulumi vs. Terraform**
------------------------

Terraform and Pulumi are key players in the IaC arena, each offering unique features for infrastructure automation. Terraform shines in its broad compatibility with cloud providers such as [AWS](https://www.env0.com/blog/deploying-amazon-elastic-kubernetes-service-aws-eks-with-terraform), [Azure](https://www.env0.com/blog/how-to-integrate-azure-devops-with-env0), and [GCP](https://docs.env0.com/docs/gcp-logging). 

The use of its domain-specific language, Hashicorp Configuration Language (HCL), lets you adopt software engineering practices in managing infrastructure with Terraform. It operates on a declarative model, focusing on what the end state of the infrastructure should look like.

Pulumi, on the other hand, aims to deliver a developer-centric experience by offering support for multiple languages including TypeScript, Go, .NET, Python, and Java. Its architecture is equipped with essentials such as a language host, command-line interface, and state management capabilities.

While Terraform specializes in a focused, declarative approach with its HCL, Pulumi offers broader language support and flexibility, making each tool uniquely suited for various cloud computing tasks and environments.

### **Key Features Comparison**

Terraform stands out with a broad set of features such as its declarative approach, extensive platform support, and a wealth of community [modules](https://www.env0.com/blog/terraform-modules). 

It allows users to express infrastructure requirements in a simple yet powerful way and integrates with a diverse selection of plugins and tools created by the community.

Meanwhile, with its user-friendly interface, extensive integrations, and native provider support, Pulumi becomes a compelling choice for developers working with Infrastructure-as-Code.

One of Pulumi’s standout features is its Dynamic Provider Support, which enables the tool to create Terraform providers and support new resources and features at a much faster pace than Terraform. 

This allows Pulumi to stay up-to-date with the latest cloud or SaaS features and resources, ensuring developers can access cutting-edge tools and technologies.

Here is a high-level comparison of the two tools:

![High-level comparison of the two tools](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/179n22y8usrmpbwk7ksh.png)


### **Pros & Cons**

Another way to look at the two tools is to pit their pros and cons against each other. I do that here in this chart, that dives a little deeper into the contrasts of the previous section.

![Pros & Cons](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x61t0lyj2k5lkgdw2vrk.png)


‍**When to Choose Terraform**
-----------------------------

1.  **Well-Established Infrastructure**: When you're working with traditional VMs, networking configurations, and databases, Terraform has a strong track record here.
2.  **Declarative Code:** If you prefer infrastructure to be declared in a domain-specific language designed solely for that purpose, Terraform's HCL is your friend.
3.  **Multi-Cloud & Provider Ecosystem**: Massive selection of providers and modules. Great if you have a heterogeneous environment with multiple clouds or SaaS services.
4.  **Community and Resources**: Abundance of tutorials, courses, and third-party tools. Also, a very active community.

**When to Choose Pulumi**
-------------------------

1.  **Full Programming Languages**: If you want the expressiveness and power of full-fledged languages like Python, TypeScript, or Go, Pulumi lets you code away.
2.  **Application-Oriented**: Better suited for modern, container-based, or serverless architectures. You can even deploy your app code along with your infrastructure.
3.  **Dynamic Configuration**: Need complex logic, loops, or conditionals? Pulumi's programming language support makes this a breeze.
4.  **Strong Typing and IDE Support**: With general-purpose languages, you get the benefits of strong typing and excellent IDE support for auto-completion, error-checking, etc.
5.  **Integrated Config Management**: Manage your secrets and configs in the same language as your infrastructure code, without requiring separate tools.
6.  **Open-Source**: If your organization prefers open-source tools for compliance or philosophy, HashiCorp's latest decision to [change Terraform's license to BSL](https://www.env0.com/blog/hashicorp-license-change) might deter you. Pulumi would be a better option.

In specific scenarios, such as multi-cloud infrastructure management, both Terraform and Pulumi offer valuable solutions for uniformly managing resources across different environments.

### **Licensing and How It May Affect Your Terraform vs. Pulumi Decision**

1.  **License Flexibility**: If you're comfortable with Apache 2.0, Pulumi SDK offers a pretty flexible license for most use cases.
2.  **Commercial Features**: For enterprise-grade features, you'd likely end up in a paid plan for both Terraform and Pulumi, depending on your needs.
3.  **Open-Source Commitment**: If an OSI-approved license is a strict requirement, Pulumi SDK meets that criterion.

Let's now compare Pulumi and Terraform with this simple example to create an S3 bucket in AWS.

**Terraform AWS S3 Bucket Example**
-----------------------------------

#### **Terraform Files**

Below is our **main.tf** file.

    terraform {
      required_providers {
        aws = {
          source = "hashicorp/aws"
          version = "5.19.0"
        }
      }
    }
    
    provider "aws" {
      region = "us-east-1"
    }
    
    resource "aws_s3_bucket" "terraform-bucket" {
      bucket = "env0-terraform-example"
    
      tags = {
        Name        = "My env0 Terraform Example Bucket"
        Environment = "Dev"
        Team        = "Engineering"
      }
    }
    
    
    resource "aws_s3_bucket_ownership_controls" "terraform-bucket" {
      bucket = aws_s3_bucket.terraform-bucket.id
      rule {
        object_ownership = "BucketOwnerPreferred"
      }
    }
    
    resource "aws_s3_bucket_acl" "terraform-bucket" {
      depends_on = [aws_s3_bucket_ownership_controls.terraform-bucket]
    
      bucket = aws_s3_bucket.terraform-bucket.id
      acl    = "private"
    }
    
    
    

#### **Run Terraform**

Now all I need to do is run the following commands:

    # Set AWS Access Key ID as an environment variable to authenticate with AWS.
    # Replace 'your-access-key-id' with your actual AWS Access Key ID.
    export AWS_ACCESS_KEY_ID=your-access-key-id
    
    # Set AWS Secret Access Key as an environment variable for AWS authentication.
    # Replace 'your-secret-access-key' with your actual AWS Secret Access Key.
    export AWS_SECRET_ACCESS_KEY=your-secret-access-key
    
    # Initialize Terraform project. This sets up the backend, downloads necessary providers, etc.
    terraform init
    
    # Run a Terraform plan to preview the changes that will be made to the infrastructure.
    # This step is a dry-run that shows what will happen when you actually apply the changes.
    terraform plan
    
    # Apply the planned changes to the infrastructure.
    # This step will actually create, update, or delete resources to match the configuration.
    # It will show a preview of these actions and ask for confirmation before proceeding.
    terraform apply
    

and here is the output of running Terraform:

    Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
      + create
    
    Terraform will perform the following actions:
    
      # aws_s3_bucket.terraform-bucket will be created
      + resource "aws_s3_bucket" "terraform-bucket" {
          + acceleration_status         = (known after apply)
          + acl                         = (known after apply)
          + arn                         = (known after apply)
          + bucket                      = "env0-terraform-example"
          + bucket_domain_name          = (known after apply)
          + bucket_prefix               = (known after apply)
          + bucket_regional_domain_name = (known after apply)
          + force_destroy               = false
          + hosted_zone_id              = (known after apply)
          + id                          = (known after apply)
          + object_lock_enabled         = (known after apply)
          + policy                      = (known after apply)
          + region                      = (known after apply)
          + request_payer               = (known after apply)
          + tags                        = {
              + "Environment" = "Dev"
              + "Name"        = "My env0 Terraform Example Bucket"
            }
          + tags_all                    = {
              + "Environment" = "Dev"
              + "Name"        = "My env0 Terraform Example Bucket"
            }
          + website_domain              = (known after apply)
          + website_endpoint            = (known after apply)
        }
    
      # aws_s3_bucket_acl.terraform-bucket will be created
      + resource "aws_s3_bucket_acl" "terraform-bucket" {
          + acl    = "private"
          + bucket = (known after apply)
          + id     = (known after apply)
        }
    
      # aws_s3_bucket_ownership_controls.terraform-bucket will be created
      + resource "aws_s3_bucket_ownership_controls" "terraform-bucket" {
          + bucket = (known after apply)
          + id     = (known after apply)
    
          + rule {
              + object_ownership = "BucketOwnerPreferred"
            }
        }
    
    Plan: 3 to add, 0 to change, 0 to destroy.
    aws_s3_bucket.terraform-bucket: Creating...
    aws_s3_bucket.terraform-bucket: Still creating... [10s elapsed]
    aws_s3_bucket.terraform-bucket: Creation complete after 16s [id=env0-terraform-example]
    aws_s3_bucket_ownership_controls.terraform-bucket: Creating...
    aws_s3_bucket_ownership_controls.terraform-bucket: Creation complete after 1s [id=env0-terraform-example]
    aws_s3_bucket_acl.terraform-bucket: Creating...
    aws_s3_bucket_acl.terraform-bucket: Creation complete after 0s [id=env0-terraform-example,private]
    

#### **Cleanup with Terraform**

To go ahead and clean up, run [terraform destroy](https://www.env0.com/blog/terraform-destroy-command-a-guide-to-controlled-infrastructure-removal)**.**

**Pulumi AWS S3 Bucket Example**
--------------------------------

Since you can use different programming languages with Pulumi, I'm most comfortable with Python, so we'll go with that.

To get started with Pulumi, I can use the command `pulumi new`. Here's what happens when you run `pulumi new`:

1.  **Template Selection**: Pulumi will first ask you to pick a template – AWS Python stack, an Azure TypeScript stack, or one of a number of others. You can even use your own custom template if you have one. For this tutorial, I’m using AWS Python.
2.  **Project Metadata**: You'll be prompted to fill in some metadata for your new project, like the project name, stack name, and sometimes config values that the template requires. This sets up the **pulumi.yaml** and **pulumi.\[stack-name\].yaml** files.
3.  **File Generation**: Pulumi will generate a set of files based on the template you chose. These usually include a **pulumi.yaml** file for the project metadata and source files in the language of your choice (e.g., `__main__.py` for Python and for our example).
4.  **Install Dependencies**: If the template has any dependencies (like AWS SDK for a Python AWS template), Pulumi will install them for you.
5.  **Ready to Go**: After all this, you'll have a new Pulumi project directory all set up and ready for you to start coding your infrastructure.

I can then cd into our project directory and run `pulumi up` to deploy our new stack, but first, I have to edit the generated **code in \_\_main\_\_.py** to tailor it to our specific needs.

I've taken a few screenshots of this process below.

![Selecting our aws-python template](https://assets-global.website-files.com/63eb9bf7fa9e2724829607c1/652ea9f3ab2db3a44b64af2c_4F9C6URRQQl9UgwEzUHnLvBK-UIdMRDtF0hW2ejhqoZKxZXoRh_AMKskN-l62aBYF8wGc1skdu2bVKYsnI3l5yD_CvFlqoCN6KiwsSMUnYM3kAgGVVqUrQySj5ODfuya9FKAnptJ6ZtnT_husW-aadM.png)

![Answering a few prompts and installing dependencies](https://assets-global.website-files.com/63eb9bf7fa9e2724829607c1/652ea9f4878288ffe95f0d01_iqR3RENqEgSVpnb89lpO6_cHwen9qtASUTJvCkecr79ymcyqGvTt_v9t3D2cRzFzVU_j0PAu2WR2gDKij7KKHW_nKvJhVSaEcNBBwbOHqnDTKJiY6lcml-ZTyiTBqz3y8rWx3QJ2Vli5Y0IjlEhPBZw.png)

![Our project is ready](https://assets-global.website-files.com/63eb9bf7fa9e2724829607c1/652ea9f3e582c8ebe1230a8a_t3pS193RlEVY22ul9MxziKwenSvN0uDr-xA0MJE16mFVf5Agcmr0iFmhJ0UxQsxTRkKzCoUJwqwfkyei3hOkdYLrHxfGrK6_XXXjh3hZR-2LbTOoBG0BJcsSxPHjqQa1UyGXockc2hsr4RbGWD7AqEw.png)

#### **Pulumi Files**

These are the files that were created in the previous process.

**pulumi.s3-pulumi-test.yaml**

    config:
      aws:region: us-east-1
    

**pulumi.yaml**

    name: PulumiTest
    runtime:
      name: python
      options:
        virtualenv: venv
    description: Testing Pulumi with Python for S3

**requirements.txt**

    pulumi>=3.0.0,<4.0.0
    pulumi-aws>=6.0.2,<7.0.0

This is the original **\_\_main\_\_.py** file

    """An AWS Python Pulumi program"""
    
    import pulumi
    from pulumi_aws import s3
    
    # Create an AWS resource (S3 Bucket)
    bucket = s3.Bucket('my-bucket')
    
    # Export the name of the bucket
    pulumi.export('bucket_name', bucket.id)

And below is our updated **\_\_main\_\_.py** file

    import pulumi
    from pulumi_aws import s3
    
    # Create an AWS resource (S3 Bucket)
    bucket = s3.Bucket("env0-pulumi-example",
                       bucket="env0-pulumi-example",
                       acl="private",
                       tags={
                           "Name": "My env0 Pulumi Example Bucket",
                           "Environment": "Dev",
                       })
    
    ownership = s3.BucketOwnershipControls(
        "bucket-controls",
        bucket=bucket.id,
        rule={"object_ownership": "BucketOwnerPreferred"}
    )
    
    # Export the name of the bucket
    pulumi.export("bucketName", bucket.id)

#### **Run Pulumi**

Let's now run Pulumi, but first, you'll need to log in to Pulumi since we're going to use the Pulumi SaaS to store our stack. Make sure you have a Pulumi account then run `pulumi login`. Create a token and feed it in the prompt. Now you're ready to run the following commands:

    pulumi preview
    Previewing update (s3-pulumi-test)
    
    View in Browser (Ctrl+O): https://app.pulumi.com/samgabrail/PulumiTest/s3-pulumi-test/previews/bc3ff0f6-4e84-4a80-9d64-aed495c97f4f
    
         Type                               Name                       Plan       
     +   pulumi:pulumi:Stack                PulumiTest-s3-pulumi-test  create     
     +   ├─ aws:s3:Bucket                   env0-pulumi-example        create     
     +   └─ aws:s3:BucketOwnershipControls  bucket-controls            create     
    
    
    Outputs:
        bucketName: output[string]
    
    Resources:
        + 3 to create

and when satisfied run `pulumi up` and choose 'yes' when prompted.

    pulumi up
    Previewing update (s3-pulumi-test)
    
    View in Browser (Ctrl+O): https://app.pulumi.com/samgabrail/PulumiTest/s3-pulumi-test/previews/68f6a538-b4e8-419f-95bf-6b5198a2706e
    
         Type                               Name                       Plan       
     +   pulumi:pulumi:Stack                PulumiTest-s3-pulumi-test  create     
     +   ├─ aws:s3:Bucket                   env0-pulumi-example        create     
     +   └─ aws:s3:BucketOwnershipControls  bucket-controls            create     
    
    
    Outputs:
        bucketName: output[string]
    
    Resources:
        + 3 to create
    
    Do you want to perform this update? yes
    Updating (s3-pulumi-test)
    
    View in Browser (Ctrl+O): https://app.pulumi.com/samgabrail/PulumiTest/s3-pulumi-test/updates/1
    
         Type                               Name                       Status              
     +   pulumi:pulumi:Stack                PulumiTest-s3-pulumi-test  created (3s)        
     +   ├─ aws:s3:Bucket                   env0-pulumi-example        created (0.83s)     
     +   └─ aws:s3:BucketOwnershipControls  bucket-controls            created (0.66s)     
    
    
    Outputs:
        bucketName: "env0-pulumi-example"
    
    Resources:
        + 3 created
    
    Duration: 5s

#### **View the S3 Bucket Created by Pulumi in the AWS Console**

![AWS Console showing the S3 bucket that Pulumi created](https://assets-global.website-files.com/63eb9bf7fa9e2724829607c1/652ea9f4ab66a14f114fa03f_uhheg33gBZ5lNmJqIBLUYiCTkv70DYSA5WJqDlTFrwOEAd8UprHqDJs05iWWL6KmJT0RqE3tdk-jOtfrO6rjNY9IxG_BylhQo2q0nLsn6PV2M3hEv3ldkYcJYTNvtjpouk4P4wpNj6iOLEMposQ7LQs.png)

#### **Cleanup with Pulumi**

Now go ahead and run `pulumi destroy` to clean up and delete the bucket.

**Summary**
-----------

In conclusion, both Terraform and Pulumi offer powerful features and benefits for managing and deploying infrastructure. While Terraform’s maturity and extensive community support make it a popular choice, Pulumi’s developer-friendly approach and growing ecosystem position it as an increasingly attractive alternative. 

By considering factors such as language support, state management, community resources, and specific use cases, organizations can make an informed decision between these two powerful IaC tools to best meet their infrastructure management needs. Better yet, use both with [env0](https://www.env0.com/) since it's a framework-agnostic platform.