In the fast‑moving world of cloud infrastructure, Migrate Legacy Terraform Configs to Pulumi TypeScript in 30 Minutes has become a top priority for teams that want to combine the declarative power of Terraform with the programming flexibility of TypeScript. This guide walks you through a proven workflow that takes a typical Terraform HCL project, translates it into Pulumi’s TypeScript SDK, and optimizes the result for maintainability—all within a half‑hour sprint. No need for months of rewrites or a full code audit; just a structured conversion that leverages Pulumi’s @pulumi/cli tools, automated HCL parsing, and TypeScript type inference.
Why Pulumi TypeScript? Key Benefits in 2026
- Strong typing – TypeScript’s compile‑time checks catch resource misconfigurations before deployment.
- Imperative patterns – You can loop, conditionally build, or import runtime data that static HCL can’t handle.
- Rich ecosystem – npm packages, Node.js tooling, and cloud‑native libraries integrate seamlessly.
- Unified CI/CD – Pulumi’s Cloud API and GitHub Actions templates allow a single pipeline to manage both IaC and application code.
- Zero‑config environment replication – Pulumi state files are portable JSON, making migrations smoother.
Step 1: Prepare Your Terraform Repository
Before any conversion, ensure your Terraform code is in a clean, modular state. If you have a monolithic main.tf, split it into modules/ directories. This mirrors Pulumi’s component pattern and keeps the conversion tidy.
- Run
terraform fmt: Normalizes formatting. - Validate with
terraform validate: Catch syntax errors early. - Document variables: Create a
variables.tfsummary file for later mapping to Pulumi’sConfigobject. - Export state: Save a copy of
terraform.tfstatefor reference; it’s handy when verifying resource outputs after migration.
Step 2: Install the Pulumi CLI and TypeScript Template
Start a fresh Pulumi project that matches the target infrastructure.
pulumi new typescript -n my-pulumi-project
cd my-pulumi-project
npm install @pulumi/aws @pulumi/azure @pulumi/google-cloud --save
These commands scaffold a TypeScript project, install Pulumi’s provider SDKs, and give you a clean workspace.
Step 3: Map Terraform Providers to Pulumi SDKs
Terraform’s provider "aws" maps directly to Pulumi’s @pulumi/aws module. For each provider in your Terraform code:
- Import the module:
import * as aws from "@pulumi/aws"; - Set up provider configuration if needed:
const awsProvider = new aws.Provider("aws", {region: "us-west-2"}); - Replace any provider‑specific arguments with the corresponding Pulumi properties.
Automated Provider Detection
Use the pulumi-terraform-converter script (available on GitHub as pulumi-terraform-converter) to auto‑generate import statements. This script parses main.tf and outputs a TypeScript skeleton. It’s a great starting point; fine‑tune the output manually afterwards.
Step 4: Convert Resources One‑by‑One
Terraform resources are declarative; Pulumi resources are instantiated as objects. Below is a concise pattern:
- Terraform:
resource "aws_s3_bucket" "example" { bucket = "my-bucket" } - Pulumi:
const exampleBucket = new aws.s3.Bucket("example", { bucket: "my-bucket" });
When converting, keep these rules in mind:
- Rename resource identifiers: Pulumi uses PascalCase for class names but keeps the logical ID in the constructor argument.
- Translate blocks to nested objects:
lifecycle { prevent_destroy = true }becomes{ lifecycle: { preventDestroy: true } }. - Handle interpolation: Terraform’s
${var.name}becomes${config.get("name")}or${varName}if you pull it from a variable. - Map complex expressions:
count = var.enabled ? 1 : 0becomes a conditional statement:if (config.getBoolean("enabled")) { ... }.
Batch Conversion with tf2pulumi
For large modules, run the tf2pulumi CLI. It outputs a main.ts that covers most resources, but review it carefully for provider‑specific nuances.
Step 5: Convert Variables and Outputs
Terraform variables become Pulumi Config values:
const config = new pulumi.Config();
const instanceType = config.require("instanceType");
Outputs translate to exported constants:
export const bucketName = exampleBucket.bucket;
Use pulumi config set to preload default values for local testing.
Step 6: Handle Data Sources and Remote State
Terraform data sources (e.g., data "aws_ami" "eks_node" { ... }) map to Pulumi calls that return a Promise. For example:
const ami = await aws.ec2.getAmi({
filters: [{ name: "name", values: ["amzn2-ami-hvm-*-x86_64-ebs"] }],
owners: ["amazon"],
});
Use async/await for resources that depend on data source results. Wrap the entire index.ts in an async function if necessary.
State Migration
While Pulumi uses a JSON .pulumi directory, you can import Terraform state by running:
pulumi state import aws:s3/bucket:Bucket exampleBucket ${terraformStateId}
Replace ${terraformStateId} with the resource ID from the Terraform state file.
Step 7: Optimize for TypeScript Idioms
Now that the code compiles, polish it with TypeScript best practices:
- Leverage interfaces: Define resource property types for complex blocks.
- Use utility functions: Create
createVpc(),createS3Bucket()helpers to encapsulate recurring logic. - Employ enums: For static values like instance types or regions.
- Split into components: Use Pulumi
ComponentResourcefor reusable infrastructure blocks.
Step 8: Validate, Test, and Deploy
Run Pulumi preview to compare the desired state with the current state:
pulumi preview
Use unit tests with jest and @pulumi/pulumi/testing to assert that resource configurations match expectations. Finally, deploy:
pulumi up
Review the stack outputs and confirm that resources are provisioned correctly.
Post‑Conversion: Automating Future Migrations
Embed the conversion process into your CI pipeline. For instance, add a GitHub Action that runs tf2pulumi on every pull request and flags any manual edits required. This ensures any future Terraform additions are automatically reflected in Pulumi code.
Common Pitfalls and Quick Fixes
- Missing provider configuration: Ensure each provider’s region, credentials, and endpoints are set.
- Type mismatches: Pulumi’s strong typing can surface errors early; resolve by refining interface definitions.
- Async dependencies: Remember that data source calls return promises; chain them properly.
- Unresolved interpolation: Double‑check that all
${var}placeholders have correspondingConfigentries.
Conclusion
By following these structured steps, teams can transition legacy Terraform HCL configurations to Pulumi TypeScript quickly—often within a single 30‑minute sprint. The result is a more maintainable, type‑safe IaC codebase that benefits from JavaScript tooling, modern CI/CD practices, and the flexibility to embed imperative logic where needed. This migration not only modernizes your infrastructure code but also positions your organization to adopt future cloud-native innovations with minimal friction.
