Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Pulumi vs Terraform: Real Trade-offs

By Kokil Thapa | Last reviewed: September 2026

Pulumi vs Terraform: Real Trade-offs matter the moment your stack outgrows a single VPS and a hand-edited Nginx config. Both tools provision cloud and on-prem resources from code. Both track desired state and reconcile drift. The split is not "better or worse." It is language model, ecosystem maturity, licensing posture, and who maintains the modules after launch. If you already ship Laravel apps with Terraform for VPS provisioning, Pulumi may feel familiar yet different. This guide compares both from a production engineer's view—not a vendor slide deck.

What is the difference between Pulumi and Terraform?

Terraform expresses infrastructure in HashiCorp Configuration Language (HCL). You declare resources, wire dependencies, and run terraform plan and terraform apply. Pulumi uses general-purpose languages—TypeScript, Python, Go, C#, Java, and YAML—for the same job. Under the hood, both talk to cloud APIs through provider plugins. Pulumi can even wrap existing Terraform providers, which matters when you need a niche integration on day one.

Think of Terraform as a specialised DSL tuned for infra graphs. Think of Pulumi as infra libraries inside a language you already use for APIs, tests, and CI scripts. On a client project where the team lives in TypeScript, Pulumi removes the "context switch tax" between app code and infra code. On teams that hire dedicated platform engineers, Terraform's HCL-only model is often easier to gate and review.

IaC Architecture: Two Paths to Cloud APIsTerraformHCL files (.tf)terraform CLI engineProvider pluginsPulumiTS / Python / Gopulumi CLI engineNative + TF providersShared Layer: Cloud Provider APIs (AWS, GCP, Azure, Cloudflare)State Backend: S3, GCS, Azure Blob, Pulumi Cloud, Terraform Cloud
Pulumi vs Terraform: Real Trade-offs start at the language layer but converge on providers, APIs, and remote state.

A minimal Terraform stack for an Ubuntu VPS might look like this:

# main.tf — Terraform 1.x / OpenTofu compatible
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
  backend "s3" {
    bucket = "my-org-terraform-state"
    key    = "prod/vps/terraform.tfstate"
    region = "ap-south-1"
  }
}

resource "aws_instance" "app" {
  ami           = "ami-0abc1234"
  instance_type = "t3.small"
  tags = { Name = "laravel-app-prod" }
}

The Pulumi equivalent in TypeScript uses loops, functions, and unit tests without HCL workarounds:

// index.ts — Pulumi with @pulumi/aws
import * as aws from "@pulumi/aws";

const app = new aws.ec2.Instance("app", {
    ami: "ami-0abc1234",
    instanceType: "t3.small",
    tags: { Name: "laravel-app-prod" },
});

export const publicIp = app.publicIp;

For deeper language-model context, see Pulumi IaC in real programming languages and Terraform vs Pulumi vs OpenTofu.

How does state management compare in Pulumi vs Terraform?

Both tools maintain a state file that maps logical resource names to real cloud IDs. Lose or corrupt that state, and your next apply can duplicate resources or fail to destroy orphans. Terraform stores JSON state locally by default; production teams push it to S3, GCS, or Terraform Cloud with locking via DynamoDB or native backends. Pulumi defaults to Pulumi Cloud for state and secrets, but also supports self-managed S3, Azure Blob, GCS, and local filesystem backends.

Terraform state patterns

Remote state with locking is non-negotiable for teams above one engineer. I've seen duplicate EC2 instances appear after two applies raced without a lock. The fix was DynamoDB table locking on S3—documented well in guides on managing Terraform state safely. Workspaces and separate state keys per environment keep prod and staging isolated.

Pulumi state patterns

Pulumi encrypts secrets in state by default when using Pulumi Cloud. Self-hosted backends require you to configure secret providers—AWS KMS, GCP KMS, or passphrase-based encryption. Stack names (org/project/prod) replace Terraform workspaces for environment separation. Outputs are first-class and typed in your chosen language.

Plan / Apply Workflow with Remote StateCode ChangePlan / PreviewAcquire LockApplyTerraformS3 + DynamoDB lockterraform.tfstate JSONPulumiPulumi Cloud or S3Stack checkpoints + secretsDrift DetectionScheduled plan, CloudWatch alarms, policy scans (Checkov, OPA)
Both tools follow plan-lock-apply cycles; remote state and drift checks are where operational discipline matters most.

Drift happens when someone edits a security group in the AWS console. Terraform and Pulumi both detect it on the next plan. For Terraform-heavy shops, read Terraform drift detection strategies and scan configs with Checkov for misconfigurations. Pulumi offers Policy as Code via CrossGuard—similar in intent to Sentinel or OPA.

Which teams should choose Pulumi over Terraform?

Choose Pulumi when your infra authors are application developers first. If your repo already holds Laravel services, Vue components, and GitLab CI YAML, TypeScript or Python Pulumi stacks sit naturally beside them. You can import shared constants, reuse validation libraries, and write Jest or pytest coverage for infra logic—something HCL makes awkward without code generation.

Choose Terraform when platform engineering is a dedicated function and HCL fluency is a hiring filter. Terraform's public registry lists thousands of verified modules for VPCs, EKS, RDS, and Cloudflare. Composing modules with terragrunt keeps DRY across accounts—see Terragrunt for DRY Terraform. For multi-account AWS layouts, multi-account AWS with Terraform remains the most documented path in 2026.

CriterionTerraform / OpenTofuPulumiPractical verdict
LanguageHCL (+ CDKTF for bindings)TypeScript, Python, Go, C#, Java, YAMLPulumi wins for dev-centric teams; Terraform wins for infra-only teams
Module ecosystemMassive public registrySmaller; wraps TF providersTerraform for off-the-shelf modules
Testingterratest, kitchen-terraform, plan-only CINative unit tests in your languagePulumi for test-driven infra
License (2026)BSL 1.1 (HashiCorp); OpenTofu MPL-2.0Apache 2.0 engineOpenTofu or Pulumi if BSL is a blocker
CI/CD fitUniversal; every pipeline example existsStrong; needs pulumi CLI in runnersTie—both work in GitLab CI and GitHub Actions
Learning curveNew DSL, shallow at firstNo new DSL if you know the languagePulumi lowers entry for devs; Terraform for ops hires
Secrets in stateMark sensitive; external vaultsBuilt-in encryption optionsPulumi slightly easier for small teams
Vendor maturity12+ years, huge communityYounger, fast releasesTerraform for conservative enterprises

In my experience maintaining sister sites on a shared Deployer 7 and GitLab CI pipeline, Terraform provisions the EC2 and RDS layer. Application deploys stay separate. That split works because ops owns the Terraform repo and devs never touch it. A startup with two full-stack engineers might prefer one monorepo where Pulumi and Laravel share TypeScript types for environment config.

How do you run Pulumi and Terraform in production CI/CD?

Both tools belong in pipeline stages: lint, plan on pull requests, apply on merge to main with approval gates. Never apply from a laptop to production without the same checks CI enforces. I've encountered broken pipelines from stale SSH keys and wrong CLI paths—the same class of failure hits both tools.

Terraform in GitLab CI

  1. Install Terraform or OpenTofu CLI in the job image.
  2. Configure AWS credentials via OIDC or masked CI variables.
  3. Run terraform init -backend-config=... against remote state.
  4. Run terraform plan -out=plan.cache on merge requests.
  5. Run terraform apply plan.cache only on protected branches.
# .gitlab-ci.yml excerpt
plan:
  stage: test
  script:
    - terraform init -input=false
    - terraform plan -input=false -out=plan.cache
  artifacts:
    paths: [plan.cache]

apply:
  stage: deploy
  when: manual
  script:
    - terraform init -input=false
    - terraform apply -input=false plan.cache
  only: [main]

Pulumi in GitLab CI

Pulumi's CI flow mirrors Terraform with different commands:

# .gitlab-ci.yml excerpt for Pulumi
preview:
  stage: test
  script:
    - pulumi login --cloud-url s3://my-pulumi-state
    - pulumi stack select org/project/prod
    - pulumi preview --diff

up:
  stage: deploy
  when: manual
  script:
    - pulumi up --yes
  only: [main]

For managed runners and policy enforcement, compare Spacelift vs Terraform Cloud. Pulumi Cloud offers parallel features: RBAC, audit logs, and team stacks. On budget-sensitive Nepal projects—often Rs 15,000–40,000/month hosting (~USD 110–295)—self-managed S3 state avoids SaaS per-seat costs.

IaC Tool Decision TreeStart: New IaC adoptionBSL license a blocker?YesNoOpenTofu or PulumiApache 2.0 / MPL pathsTeam skill checkDevs vs platform opsChoose PulumiApp devs write infraChoose TerraformRegistry modules, ops team
Use this decision tree when evaluating Pulumi vs Terraform: Real Trade-offs for licensing, team shape, and module reuse.

After infra is live, application deployment still needs attention. Several legal-tech and booking platforms I've shipped—such as Adventure Third Pole Trek—use Deployer for zero-downtime releases while Terraform owns the server layer. That separation keeps blast radius small. For ongoing ops, Linux system administration and support and maintenance cover what IaC does not: PHP-FPM tuning, log rotation, and backup verification.

What are the licensing and migration trade-offs in 2026?

HashiCorp changed Terraform's license to Business Source License (BSL) 1.1 in 2023. That shift pushed many organisations toward OpenTofu, the MPL-2.0 fork governed by the Linux Foundation. OpenTofu remains API-compatible with Terraform 1.x workflows. Pulumi's open-source engine stayed Apache 2.0, which matters for companies with strict OSS policies or government clients.

Official references: the HashiCorp Terraform documentation covers BSL Terraform features, while Pulumi documentation describes stacks, providers, and CrossGuard policies. For the open fork, the OpenTofu docs track migration paths from HashiCorp Terraform.

Migrating between tools

Pulumi offers pulumi convert and Terraform import workflows to ingest existing .tf state. The result is rarely production-ready without cleanup—HCL modules become verbose generated code. The pragmatic path is greenfield stacks or incremental imports per resource group. Moving from Pulumi to Terraform means exporting state and rewriting logic in HCL; there is no one-click reverse converter worth trusting blindly.

Reuse existing Terraform knowledge via Terraform modules, variables, locals, and outputs, and provider version pinning. If you standardise on OpenTofu, treat provider blocks the same way—you pin sources and run tofu init instead of terraform init.

Production Stack: IaC + App DeployGit Pushmain branchIaC PipelineTF or Pulumi applyEC2 / RDS / DNSCloud resourcesApp DeployDeployer 7Runtime LayerUbuntu 24 · PHP 8.4/8.5 FPM · MySQL 8.4 · Redis 8.x · Laravel 12/13IaC OwnsVPC, SG, instances, RDS, S3 bucketsDeployer OwnsSymlink releases, .env, storage, cron
Split IaC provisioning from application deploy: a pattern used on production Laravel stacks with GitLab CI and Deployer 7.

Validate generated JSON configs in CI with a JSON formatter before they reach production pipelines. For greenfield platforms needing custom workflows—not just VMs—custom software development and enterprise application development cover the application layer IaC leaves open.

Key Takeaways

  • Terraform and OpenTofu excel when dedicated ops staff reuse public modules and standardise HCL across accounts.
  • Pulumi fits dev-heavy teams that want TypeScript or Python tests, loops, and shared libraries beside application code.
  • Remote state with locking is mandatory for both tools—never run production applies without it.
  • BSL licensing pushed many teams to OpenTofu or Pulumi; evaluate legal requirements before committing.
  • Keep IaC (servers, networks, databases) separate from app deploy (Deployer, CI) to limit blast radius.
  • Run plan/preview on every pull request and apply only from protected branches with manual approval.

People Also Ask

Can Pulumi use Terraform providers?

Yes. Pulumi's Terraform Bridge wraps many HashiCorp providers so you can consume the same AWS, Cloudflare, and GitHub resources while writing TypeScript or Python. Coverage is broad but not universal—check the Pulumi registry before assuming a niche provider exists.

Is Terraform dead after the BSL change?

No. Terraform remains widely deployed, and OpenTofu offers a community-governed alternative with compatible workflows. The change mainly affects redistribution and competitive products—not teams that run CLI applies internally.

Which tool is easier for beginners?

Developers already comfortable in Python or TypeScript often learn Pulumi faster because they skip HCL. Operators with prior Ansible or CloudFormation experience frequently prefer Terraform's declarative model and extensive tutorial ecosystem, including Terraform for VPS provisioning.

Do you need a SaaS backend for state?

No. Both tools support self-managed S3-compatible backends. SaaS options—Terraform Cloud, Pulumi Cloud, Spacelift—add RBAC and audit trails. Small teams on tight budgets typically start with S3 and DynamoDB locking, then upgrade when compliance demands it.

Pick the tool your team will actually maintain

Pulumi vs Terraform: Real Trade-offs reduce to team shape, licensing comfort, and module reuse—not abstract performance scores. Terraform and OpenTofu win when platform engineers curate reusable HCL and you want the largest public module library. Pulumi wins when infra lives beside Laravel or Node code and you want real tests, typed outputs, and one language across the repo. Either path beats manual console clicks. Start with remote state, PR-based plans, and a clear split between provisioning and app deploy. If you want help designing that pipeline for a Nepal or remote project, contact us or explore more about my DevOps and Laravel work. Related reading: Pulumi in real programming languages and automate off-site backups to S3.

Frequently Asked Questions

Terraform expresses infrastructure in HashiCorp Configuration Language (HCL). You declare resources, wire dependencies, and run terraform plan and terraform apply. Pulumi uses general-purpose languages such as TypeScript, Python, Go, C#, Java, and YAML for the same job. Both talk to cloud APIs through provider plugins. Think of Terraform as a specialised DSL tuned for infrastructure graphs. Pulumi is infrastructure libraries inside a language you already use for APIs, tests, and CI scripts. The split is language model, not abstract performance.

Yes. Pulumi's Terraform Bridge wraps many HashiCorp providers so you can use the same AWS, Cloudflare, and GitHub resources while writing TypeScript or Python. Coverage is broad but not universal.

No. Terraform remains widely deployed, and OpenTofu offers a community-governed MPL-2.0 alternative with compatible workflows. The BSL change mainly affects redistribution and competitive products, not teams running CLI applies internally.

Developers already comfortable in Python or TypeScript often learn Pulumi faster because they skip learning HCL. Operators with prior Ansible or CloudFormation experience frequently prefer Terraform's declarative model and its extensive tutorial ecosystem, including guides on Terraform for VPS provisioning. Pulumi lowers entry for developers; Terraform is often easier when platform engineering is a dedicated function and HCL fluency is a hiring filter. Neither tool removes the need to understand cloud resources, remote state, and plan-apply discipline.

No. Both tools support self-managed S3-compatible backends. SaaS options such as Terraform Cloud, Pulumi Cloud, and Spacelift add RBAC and audit trails when you need them.

Both maintain a state file mapping logical resource names to real cloud IDs. Lose or corrupt that state and your next apply can duplicate resources or fail to destroy orphans. Terraform stores JSON state locally by default; production teams push it to S3, GCS, or Terraform Cloud with locking via DynamoDB or native backends. Pulumi defaults to Pulumi Cloud but also supports self-managed S3, Azure Blob, GCS, and local filesystem backends. Remote state with locking is non-negotiable for teams above one engineer.

Choose Pulumi when infra authors are application developers first. If your repo already holds Laravel services, Vue components, and GitLab CI YAML, TypeScript or Python Pulumi stacks sit naturally beside them. You can import shared constants, reuse validation libraries, and write Jest or pytest coverage for infra logic. Choose Terraform when platform engineering is a dedicated function, HCL fluency is a hiring filter, and you want thousands of verified public registry modules for VPCs, EKS, RDS, and Cloudflare without writing abstractions yourself.

Terraform's public registry lists thousands of verified modules for common patterns like VPCs, EKS, RDS, and Cloudflare. Composing modules with Terragrunt keeps configurations DRY across accounts. Pulumi's ecosystem is smaller but can wrap existing Terraform providers through its bridge. For multi-account AWS layouts, multi-account AWS with Terraform remains the most documented path in 2026. If your team wants off-the-shelf reuse and standardised HCL across accounts, Terraform wins. Pulumi wins when you prefer native abstractions and real programming language constructs over composing HCL modules.

Both belong in pipeline stages: lint, plan or preview on pull requests, apply on merge to main with approval gates. Never apply to production from a laptop without the same checks CI enforces. For Terraform in GitLab CI, install the CLI, configure AWS credentials via OIDC or masked variables, run terraform init against remote state, terraform plan -out=plan.cache on merge requests, and terraform apply plan.cache only on protected branches with manual approval. Pulumi mirrors this with pulumi login, pulumi stack select, pulumi preview on PRs, and pulumi up on main.

HashiCorp changed Terraform to Business Source License 1.1 in 2023, pushing many organisations toward OpenTofu, the MPL-2.0 fork governed by the Linux Foundation. OpenTofu remains API-compatible with Terraform 1.x workflows. Pulumi's open-source engine stayed Apache 2.0, which matters for strict OSS policies or government clients. Pulumi offers pulumi convert and Terraform import workflows, but results are rarely production-ready without cleanup. Moving from Pulumi to Terraform means exporting state and rewriting logic in HCL with no trustworthy one-click reverse converter.

Terraform marks values sensitive and typically relies on external vaults for long-term secret storage. Pulumi encrypts secrets in state by default when using Pulumi Cloud. Self-hosted Pulumi backends require configuring secret providers such as AWS KMS, GCP KMS, or passphrase-based encryption. For small teams, Pulumi's built-in encryption options are slightly easier out of the box. Both tools still demand remote state with locking and careful access control regardless of which secret strategy you choose.

Drift happens when someone edits a security group or other resource in the AWS console outside your code. Terraform and Pulumi both detect it on the next plan or preview. For Terraform-heavy shops, dedicated drift detection strategies and scanning configs with Checkov for misconfigurations are common practice. Pulumi offers Policy as Code via CrossGuard, similar in intent to Sentinel or OPA. Operational discipline matters more than the tool: run plan or preview on every pull request and treat console edits as temporary unless you commit the change back to code.

Both tools themselves are free at the CLI level for self-managed workflows. SaaS backends such as Terraform Cloud, Pulumi Cloud, and Spacelift add per-seat costs that small teams often defer. On budget-sensitive Nepal projects, often Rs 15,000 to 40,000 per month for hosting, roughly USD 110 to 295, self-managed S3 state with DynamoDB locking avoids SaaS fees while still giving you remote state and locking. Upgrade to managed platforms when compliance demands RBAC, audit logs, and team stack governance that self-hosted backends do not provide out of the box.

Yes. In my experience maintaining sister sites on a shared Deployer 7 and GitLab CI pipeline, Terraform provisions the EC2 and RDS layer while application deploys stay separate via Deployer. That split works because ops owns the Terraform repo and developers never touch it. A startup with two full-stack engineers might prefer one monorepo where Pulumi and Laravel share TypeScript types for environment config. Either way, keeping IaC for servers, networks, and databases separate from app deploy limits blast radius when a bad release or a bad infra change occurs.

Choose OpenTofu when BSL licensing is a blocker for your organisation's redistribution, competitive product, or strict open-source policy requirements. OpenTofu is the MPL-2.0 fork governed by the Linux Foundation and remains API-compatible with Terraform 1.x workflows. If you standardise on OpenTofu, treat provider blocks the same way: pin sources and run tofu init instead of terraform init. Teams that only run internal CLI applies and are comfortable with HashiCorp's BSL may stay on Terraform. Evaluate legal requirements before committing, especially alongside Pulumi's Apache 2.0 engine as an alternative path.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: