Migrating Infrastructure as Code is a high-stakes refactoring operation. A full "big bang" cutover from Terraform to Pulumi (or vice versa) is often too risky for critical environments. The safer, more pragmatic strategy is to run both tools side-by-side, managing different slices of your infrastructure concurrently during a gradual transition.
The core challenge is state isolation. Both tools must have a clear, non-overlapping jurisdiction to prevent destructive interference. The most effective pattern I've used is segmentation by resource type or logical component, not by environment. For example, you might let Pulumi manage all Kubernetes (EKS/AKS) resources and Terraform manage the underlying network (VPC, subnets).
### Implementation Strategy
1. **Define a Shared State Boundary:** Use a data source in one tool to read the output of the other. This creates a one-way dependency and explicit contract.
In Pulumi (TypeScript), you can import a Terraform-managed VPC ID:
```typescript
import * as aws from "@pulumi/aws";
import * as terraform from "@pulumi/terraform";
// Reference the Terraform state for the VPC
const terraformState = new terraform.state.RemoteStateReference("terraform-vpc", {
backendType: "s3",
args: {
bucket: "my-infra-state",
key: "envs/prod/network.tfstate"
}
});
// Use the VPC ID from Terraform to create a Pulumi-managed resource
const securityGroup = new aws.ec2.SecurityGroup("app-sg", {
vpcId: terraformState.getOutput("vpc_id"),
ingress: [{ protocol: "tcp", fromPort: 443, toPort: 443, cidrBlocks: ["0.0.0.0/0"] }],
});
```
In Terraform, you can use the `terraform_remote_state` data source to read Pulumi outputs (Pulumi can export its state in a compatible format).
2. **Orchestration via CI/CD:** Your pipeline must serialize operations where dependencies cross tool boundaries. A simple approach is a two-stage pipeline:
* Stage 1: Apply Terraform modules for the "upstream" resources (e.g., network).
* Stage 2: Apply Pulumi programs that depend on those Terraform outputs.
This prevents race conditions and ensures the dependency graph is respected.
3. **State Backend Coexistence:** Use separate state files or prefixes within the same backend (e.g., different S3 paths or separate Azure Storage containers). Clear naming is crucial: `terraform/prod/network.tfstate` and `pulumi/prod/apps.json`.
### Key Considerations
* **Read-Only First:** Start by having the new tool only *read* the existing tool's state. This validates your state access patterns without risk.
* **Refactor, Then Migrate:** Sometimes, it's beneficial to first refactor messy Terraform modules into cleaner, isolated components *within* Terraform. Then, migrate these cleaner units to Pulumi. This avoids porting "tech debt."
* **Validation:** Implement pre- and post-apply validation steps (e.g., using `pulumi preview` and `terraform plan`) in your pipeline to detect configuration drift early.
The primary benefit of this side-by-side approach is reduced risk and the ability to migrate at your own pace. The main cost is increased complexity in CI/CD and the need for the team to context-switch between two toolchains temporarily. For large, business-critical infrastructure, this trade-off is almost always worthwhile.
benchmark or bust
benchmark or bust
The state boundary trick works, but you're leaving out the biggest headache: state locking. If your terraform state is in S3 with DynamoDB and your pulumi state is in their service or a different bucket, you've got no cross-tool lock coordination. I've seen a junior engineer run a terraform apply while a pulumi up was halfway through updating a shared security group dependency. The result was about what you'd expect.
You also need to be religious about output exports. If Terraform owns the VPC, that vpc_id output better be in a consistent, versioned place Pulumi can always find it. A Terraform state backend that supports easy data source lookups is non-negotiable. Using the S3 backend for Terraform and then having Pulumi's terraform state provider read that directly is more reliable than trying to pass values through CI variables.
And for the love of all that's holy, your CI pipeline needs a manual approval gate between any terraform plan and apply when you're in this hybrid state. Automation is great until it blindly nukes a foundational resource the other tool just created.
Speed up your build