IaC Tools Compared: Bicep vs Terraform vs Pulumi

IaC Tools Compared: Bicep vs Terraform vs Pulumi

Nina Hoffmann ยท November 25, 2025
Infrastructure as CodeBicepTerraform
CloudArchitecture

Choosing an Infrastructure as Code tool is one of those decisions that sticks with a team for years. After using Bicep, Terraform, and Pulumi in production across different projects, I want to share a practical comparison to help you make an informed choice.

The Contenders

All three tools solve the same fundamental problem: defining cloud infrastructure in version-controlled, repeatable, reviewable code. But they take very different approaches.

Bicep is Microsoft's domain-specific language for Azure. It compiles to ARM templates and is tightly integrated with the Azure ecosystem.

Terraform by HashiCorp uses HCL (HashiCorp Configuration Language) and supports every major cloud provider through its provider ecosystem.

Pulumi lets you define infrastructure using general-purpose programming languages like TypeScript, Python, C#, and Go.

Deploying the Same Resource

Let us deploy an Azure Container App with all three tools to see the syntactic differences.

Bicep

param location string = resourceGroup().location
param environmentId string

resource containerApp 'Microsoft.App/containerApps@2024-03-01' = {
  name: 'order-api'
  location: location
  properties: {
    managedEnvironmentId: environmentId
    configuration: {
      ingress: {
        external: true
        targetPort: 8080
      }
    }
    template: {
      containers: [
        {
          name: 'order-api'
          image: 'myregistry.azurecr.io/order-api:latest'
          resources: {
            cpu: json('0.5')
            memory: '1Gi'
          }
        }
      ]
      scale: {
        minReplicas: 1
        maxReplicas: 10
      }
    }
  }
}

Terraform

resource "azurerm_container_app" "order_api" {
  name                         = "order-api"
  container_app_environment_id = azurerm_container_app_environment.main.id
  resource_group_name          = azurerm_resource_group.main.name
  revision_mode                = "Single"

  ingress {
    external_enabled = true
    target_port      = 8080
  }

  template {
    container {
      name   = "order-api"
      image  = "myregistry.azurecr.io/order-api:latest"
      cpu    = 0.5
      memory = "1Gi"
    }

    min_replicas = 1
    max_replicas = 10
  }
}

Pulumi (C#)

var containerApp = new ContainerApp("order-api", new ContainerAppArgs
{
    ResourceGroupName = resourceGroup.Name,
    ManagedEnvironmentId = environment.Id,
    Configuration = new ConfigurationArgs
    {
        Ingress = new IngressArgs
        {
            External = true,
            TargetPort = 8080,
        },
    },
    Template = new TemplateArgs
    {
        Containers =
        {
            new ContainerArgs
            {
                Name = "order-api",
                Image = "myregistry.azurecr.io/order-api:latest",
                Resources = new ContainerResourcesArgs
                {
                    Cpu = 0.5,
                    Memory = "1Gi",
                },
            },
        },
        Scale = new ScaleArgs
        {
            MinReplicas = 1,
            MaxReplicas = 10,
        },
    },
});

State Management

This is where the tools diverge significantly.

Bicep has no state file. Azure Resource Manager is the source of truth. Deployments are idempotent and always compare desired state against what exists in Azure. This is both a strength (no state file to manage) and a weakness (no way to track resources that were removed from your templates).

Terraform maintains a state file that maps your configuration to real resources. You must store this state securely, typically in Azure Storage or Terraform Cloud. State locking prevents concurrent modifications.

Pulumi also uses a state file, stored in Pulumi Cloud, an S3 bucket, or Azure Blob Storage. The experience is similar to Terraform but with better default encryption.

Multi-Cloud Support

If you are Azure-only, Bicep is the natural choice. Its resource type system maps one-to-one with Azure APIs, so new Azure features are available immediately.

Terraform and Pulumi both support multi-cloud deployments. Terraform has the largest provider ecosystem with thousands of community-maintained providers. Pulumi supports most Terraform providers through a bridging mechanism.

Developer Experience

Bicep has excellent VS Code support with IntelliSense, type checking, and the Bicep visualizer. The learning curve is gentle if you are already familiar with Azure.

Terraform has solid tooling and the largest community. HCL is easy to learn but can feel limiting when you need complex logic like conditional resources or dynamic blocks.

Pulumi wins for developer experience if your team already knows C#, TypeScript, or Python. You get full IDE support, unit testing with standard frameworks, and the ability to use loops, conditionals, and abstractions naturally:

// Pulumi: Create multiple environments with a loop
var environments = new[] { "dev", "staging", "prod" };
foreach (var env in environments)
{
    var rg = new ResourceGroup($"rg-{env}", new ResourceGroupArgs
    {
        Location = "westeurope",
        Tags = { { "environment", env } },
    });
}

Doing the same in Terraform requires for_each or count, which are more verbose and less intuitive.

Testing

Bicep: Limited to what-if deployments and the new Bicep Testing Framework (still maturing).

Terraform: terraform plan for preview, plus third-party tools like Terratest for integration tests.

Pulumi: Full unit testing with your language's test framework. You can mock cloud resources and verify configuration in isolation. This is Pulumi's strongest advantage.

My Recommendation

  • Azure-only, small to medium team: Use Bicep. It is simple, well-supported, and requires no state management.
  • Multi-cloud or large Terraform investment: Stick with Terraform. The ecosystem and community support are unmatched.
  • Developer-heavy team that values testability: Consider Pulumi. The ability to use real programming languages and standard testing tools is compelling.

There is no wrong choice among these three. Pick the one that aligns with your team's skills, cloud strategy, and operational maturity.


Comments (2)

Hannah Mar 02, 2026 · 17:08

I have a question: does this also apply to older versions?

Ben Mar 06, 2026 · 20:08

Good question, I'd like to know too.

Clara Mar 07, 2026 · 20:08

Interesting thought, thanks for adding that.

Anna Mar 03, 2026 · 17:08

Thanks for sharing — very helpful.


Leave a comment

An unhandled error has occurred. Reload ๐Ÿ—™

Rejoining the server...

Rejoin failed... trying again in seconds.

Failed to rejoin.
Please retry or reload the page.

The session has been paused by the server.

Failed to resume the session.
Please retry or reload the page.