Seamlessly Transitioning Infrastructure as Code: A Step-by-Step Guide to Migrating from Terraform to Pulumi
Forget the hype around switching IaC tools. The real value lies in how you migrate without disrupting existing workflows. This guide breaks down the practical steps and nuances of transitioning from Terraform’s declarative style to Pulumi’s imperative approach — mastering the migration rather than just changing the tool.
Infrastructure as Code (IaC) is fundamental in modern development workflows, automating resource provisioning and management at scale. Terraform has long been a staple in this space with its human-readable declarative language (HCL). However, as cloud infrastructures grow more complex and organizations seek deeper integration with existing DevOps toolchains, Pulumi’s multi-language imperative approach is gaining traction.
Pulumi allows you to write infrastructure code using familiar programming languages like TypeScript, Python, Go, or C#. This flexibility empowers developers by integrating IaC more tightly with application code and expanding logic possibilities that are awkward or impossible in declarative formats.
If you’re considering migrating from Terraform to Pulumi, here’s a practical walkthrough to guide your transition smoothly and effectively — avoiding common traps and focusing on continuity.
Why Migrate from Terraform to Pulumi?
Before diving into the how-to, it’s worth revisiting why this change can be a game-changer:
- Multi-Language Support: Use real programming languages (e.g., Python, TypeScript) instead of HCL.
- Imperative Programming Model: Write conditional logic and loops natively.
- Better Code Reuse: Leverage IDE support, linting, debugging, and testing.
- Unified Tooling: Manage cloud infrastructure alongside application lifecycle code.
Still, migrating entails challenges including understanding Pulumi’s model differences, translating existing state files safely, and adapting your team’s workflows.
Step 1: Audit Your Existing Terraform Infrastructure
Before migrating anything:
- Identify all Terraform configurations that need migration.
- List all providers/plugins used.
- Review dependency graphs, modules in use, backend configurations for state storage.
- Backup your Terraform state files (
terraform.tfstate
) — these will be crucial for mapping resources later.
Example:
terraform init
terraform plan
cp terraform.tfstate terraform.tfstate.backup
Step 2: Install Pulumi & Set Up Your Environment
Pulumi needs to be installed on your machines or CI environments.
# Install via npm for JavaScript/TypeScript
npm install -g @pulumi/cli
# Or via Homebrew (macOS)
brew install pulumi
Log in to Pulumi for state management (Cloud Console or self-hosted backend):
pulumi login
Tip: You can also configure Backends like S3 or Azure Blob Storage if needed.
Step 3: Initialize a New Pulumi Project
Create a new project in your desired language.
For example, a TypeScript AWS project:
pulumi new aws-typescript -n my-infra-pulumi
cd my-infra-pulumi
This scaffolds Pulumi.yaml
, installs dependencies, and sets up basic files.
Step 4: Import Existing Resources into Pulumi State
Pulumi has a resource import feature that lets you bring existing managed resources under its control without recreating them. This step avoids downtime or conflicts and preserves your actual infrastructure state.
Example of importing an AWS S3 bucket:
pulumi import aws:s3/bucket:Bucket myBucket my-existing-bucket-name
You can script multiple imports based on your Terraform resources list. This results in Pulumi adding these imported resources into its state but does not create any code yet.
Note: You must write resource declarations matching these imports next.
Step 5: Translate Terraform Configurations into Pulumi Code
This is the heart of migration — converting HCL *.tf files into imperative code using the Pulumi SDK for your chosen language.
Key differences to note:
Terraform (HCL) | Pulumi (TypeScript example) |
---|---|
Declarative .tf files | Imperative JS/Python/Go/C# code |
Variables & outputs | Variables/constants & functions |
Modules | Component Resources / classes |
count , for_each | Native loops (for , .map ) |
Resource dependencies | Handled by resource references directly |
Example conversion from HCL to TypeScript
Terraform HCL
resource "aws_s3_bucket" "bucket" {
bucket = "my-tf-bucket"
acl = "private"
tags = {
Environment = "Dev"
Team = "Infra"
}
}
Pulumi TypeScript
import * as aws from "@pulumi/aws";
const bucket = new aws.s3.Bucket("my-pulumi-bucket", {
bucket: "my-tf-bucket",
acl: "private",
tags: {
Environment: "Dev",
Team: "Infra",
},
});
Notice that while keys are similar, the syntax is full JS objects here — allowing for variables, conditionals, loops tightly integrated with logic in your language of choice.
Step 6: Refactor Variables and Outputs
Terraform variable blocks map roughly to parameters/constants or config variables in the target language; outputs map to exported values or stack outputs.
For example:
Terraform
variable "bucket_name" {
default = "my-tf-bucket"
}
output "bucket_arn" {
value = aws_s3_bucket.bucket.arn
}
Pulumi
const config = new pulumi.Config();
const bucketName = config.get("bucket_name") || "my-tf-bucket";
const bucket = new aws.s3.Bucket("my-pulumi-bucket", {
bucket: bucketName,
});
export const bucketArn = bucket.arn;
You will need to create corresponding configuration files for stack variables or pass inputs dynamically using CLI options or environment variables.
Step 7: Test Your New Pulumi Project Locally
Run preview commands and diff outputs before deploying:
pulumi preview # Shows proposed changes without applying them
pulumi up # Applies changes interactively with confirmation prompt
Make sure no unintended deletions or recreations happen by cross-checking with your current infra status.
Use pulumi stack export
for snapshots of the deployment state if needed for audits or rollbacks.
Step 8: Integrate Into Your CI/CD Pipeline
Update build scripts and pipelines replacing terraform plan/apply
steps with pulumi preview/up
. Take advantage of Pulumi’s automation API if you want programmatic control over deployments beyond CLI usage.
Example GitHub Actions snippet:
steps:
- uses: actions/checkout@v2
- uses: pulumi/actions@v3
with:
command: up --yes
env:
PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_ACCESS_TOKEN }}
Tips for a Smooth Migration Experience
- Incremental Migration: Start small by migrating non-critical resources first.
- State Management: Avoid dual control — pick either Terraform or Pulumi during migration phases clearly.
- Team Training: Familiarize engineers with programming idioms used by Pulumi.
- Leverage Examples: Look into open-source repos with similar cloud infra on Pulumi.
- Use Component Resources: Encapsulate complex modules as reusable components/classes in code — promoting modularity beyond what Terraforms modules allow.
Wrapping Up
Transitioning from Terraform to Pulumi goes beyond switching tools — it’s embracing a richer programmable model for IaC that enhances flexibility while potentially increasing productivity. With proper planning—auditing current infra, importing states carefully, rewriting configs thoughtfully—and cautious testing/deployment practices you can migrate confidently without disrupting live services or workflows.
The key is mastering the migration process itself so that you truly unlock what modern infrastructure as code can offer today and tomorrow!
Happy coding your cloud infrastructure!
If you found this guide helpful or want example repos/code snippets tailored for your specific provider/language combo, let me know in the comments below!