1. Docs
  2. Pulumi IaC
  3. Get started
  4. Pulumi for Terraform Users
  5. Next Steps

Next Steps

    Congratulations!

    You’ve completed the Pulumi for Terraform Users guide and learned how to:

    • Reference existing Terraform state files from Pulumi
    • Use any Terraform provider in Pulumi programs
    • Import and use Terraform modules directly
    • Convert HCL code to Pulumi when beneficial
    • Orchestrate both Terraform and Pulumi deployments together

    What’s next?

    Based on your journey through this guide, consider these next steps:

    1. Start small: Begin with a simple coexistence pattern in your current environment
    2. Experiment: Try different integration approaches to find what works for your team
    3. Build expertise: Invest in learning both Terraform and Pulumi deeply
    4. Share knowledge: Document your integration patterns for your team
    5. Consider migration: When ready, plan a gradual migration to Pulumi for new projects

    Our goal is to empower you to use the right tool for the job while maintaining a cohesive infrastructure management strategy.

    Advanced integration patterns

    Beyond the examples shown, there are some more advanced integration patterns you can employ. These will be very dependent on your particular needs, so take these recommendations as a general guide to some strategies you can use to manage more complex environments.

    Multi-stack architectures

    Organize complex infrastructure with multiple interconnected stacks:

    // Core infrastructure stack (could be Terraform)
    export const vpc = new aws.ec2.Vpc("main", {
        cidrBlock: "10.0.0.0/16",
    });
    
    // Application stack (Pulumi)
    const coreInfra = new pulumi.StackReference("core-infra");
    const vpcId = coreInfra.getOutput("vpcId");
    
    // Database stack (Terraform via state reference)
    const dbState = new terraform.state.S3Reference("database", {
        bucket: "my-terraform-state",
        key: "database/terraform.tfstate",
    });
    

    Complex state referencing

    Handle complex state structures and transformations:

    // Reference multiple Terraform states
    const networkState = new terraform.state.S3Reference("network", {
        bucket: "terraform-state",
        key: "network/terraform.tfstate",
    });
    
    const securityState = new terraform.state.S3Reference("security", {
        bucket: "terraform-state",
        key: "security/terraform.tfstate",
    });
    
    // Transform and combine outputs
    const subnetIds = networkState.getOutput("private_subnet_ids");
    const securityGroupIds = securityState.getOutput("security_group_ids");
    
    // Create resources using combined state
    const cluster = new aws.ecs.Cluster("app-cluster", {
        // Configure using multiple state outputs
        configuration: {
            executeCommandConfiguration: {
                kmsKeyId: securityState.getOutput("kms_key_id"),
                logging: "DEFAULT",
            },
        },
    });
    

    Migration strategies

    Gradual migration approaches

    When ready to migrate from Terraform to Pulumi:

    1. Import existing resources: Use pulumi import to bring Terraform-managed resources under Pulumi management
    2. Parallel management: Run both tools temporarily while migrating
    3. State migration: Transfer state ownership gradually, resource-by-resource
    4. Validation: Ensure identical infrastructure before switching

    Import existing resources

    # Import existing Terraform resources into Pulumi
    $ pulumi import aws:ec2/vpc:Vpc main-vpc vpc-12345
    $ pulumi import aws:ec2/subnet:Subnet private-subnet-1 subnet-67890
    $ pulumi import aws:ecs/cluster:Cluster app-cluster my-cluster
    

    State migration utilities

    Create utilities to help with migration:

    // Migration helper utility
    export class TerraformMigrationHelper {
        constructor(private terraformStateRef: terraform.state.S3Reference) {}
    
        // Import all resources of a given type
        async importResourceType(resourceType: string, pulumiType: string) {
            const resources = await this.terraformStateRef.getOutput("resources");
            const filtered = resources.filter(r => r.type === resourceType);
    
            for (const resource of filtered) {
                console.log(`Importing ${resourceType} ${resource.name}`);
                // Use Pulumi import API to import resources
            }
        }
    
        // Validate that imported resources match Terraform state
        async validateImports() {
            // Compare Terraform state with Pulumi state
            // Report any differences
        }
    }
    

    Advanced Pulumi features

    CrossGuard for policy enforcement

    Implement infrastructure policies that work across both Terraform and Pulumi:

    // crossguard/index.ts
    import * as aws from "@pulumi/aws";
    import { PolicyPack, validateResourceOfType } from "@pulumi/policy";
    
    new PolicyPack("terraform-integration-policies", {
        policies: [
            {
                name: "require-tags",
                description: "All resources must have required tags",
                enforcementLevel: "mandatory",
                validateResource: validateResourceOfType(aws.ec2.Instance, (instance, args, reportViolation) => {
                    const requiredTags = ["Environment", "Owner", "Project"];
                    const tags = instance.tags || {};
    
                    for (const tag of requiredTags) {
                        if (!tags[tag]) {
                            reportViolation(`Missing required tag: ${tag}`);
                        }
                    }
                }),
            },
            {
                name: "no-public-buckets",
                description: "S3 buckets cannot be publicly accessible",
                enforcementLevel: "mandatory",
                validateResource: validateResourceOfType(aws.s3.Bucket, (bucket, args, reportViolation) => {
                    if (bucket.acl === "public-read" || bucket.acl === "public-read-write") {
                        reportViolation("S3 bucket cannot have public ACL");
                    }
                }),
            },
        ],
    });
    

    Automation API for programmatic control

    Embed Pulumi in applications for dynamic infrastructure management:

    // automation-api-example.ts
    import * as pulumi from "@pulumi/pulumi/automation";
    import * as aws from "@pulumi/aws";
    
    async function createEnvironmentStack(environmentName: string) {
        const stackName = `${environmentName}-app`;
    
        // Create or select stack
        const stack = await pulumi.LocalWorkspace.createOrSelectStack({
            stackName,
            projectName: "dynamic-environments",
            program: async () => {
                // Reference shared Terraform infrastructure
                const tfState = new pulumi.terraform.state.S3Reference("shared-infra", {
                    bucket: "terraform-state",
                    key: "shared/terraform.tfstate",
                });
    
                // Create environment-specific resources
                const app = new aws.ecs.Service(`${environmentName}-app`, {
                    cluster: tfState.getOutput("cluster_name"),
                    taskDefinition: tfState.getOutput("task_definition_arn"),
                    desiredCount: environmentName === "production" ? 3 : 1,
                });
    
                return {
                    appArn: app.arn,
                    appName: app.name,
                };
            },
        });
    
        // Configure stack
        await stack.setConfig("aws:region", { value: "us-west-2" });
    
        // Deploy stack
        const upResult = await stack.up({ onOutput: console.log });
    
        return upResult.outputs;
    }
    
    // Usage
    createEnvironmentStack("staging").then(outputs => {
        console.log("Staging environment created:", outputs);
    });
    

    Component resources for reusability

    Create reusable components that integrate with Terraform, behind the scenes, leaving the complexity of multi-tool management to your platform team, not your development teams:

    // components/WebApplication.ts
    import * as pulumi from "@pulumi/pulumi";
    import * as aws from "@pulumi/aws";
    import * as terraform from "@pulumi/terraform";
    
    export interface WebApplicationArgs {
        terraformInfrastructure: terraform.state.S3Reference;
        containerImage: string;
        environment: string;
        desiredCount?: number;
    }
    
    export class WebApplication extends pulumi.ComponentResource {
        public readonly service: aws.ecs.Service;
        public readonly loadBalancer: aws.lb.LoadBalancer;
        public readonly url: pulumi.Output<string>;
    
        constructor(name: string, args: WebApplicationArgs, opts?: pulumi.ComponentResourceOptions) {
            super("custom:WebApplication", name, {}, opts);
    
            const defaultParent = { parent: this };
    
            // Get infrastructure from Terraform
            const clusterName = args.terraformInfrastructure.getOutput("cluster_name");
            const vpcId = args.terraformInfrastructure.getOutput("vpc_id");
            const subnetIds = args.terraformInfrastructure.getOutput("subnet_ids");
    
            // Create load balancer
            this.loadBalancer = new aws.lb.LoadBalancer(`${name}-alb`, {
                name: `${name}-alb`,
                loadBalancerType: "application",
                subnets: subnetIds,
                internal: false,
            }, defaultParent);
    
            // Create ECS service
            const taskDefinition = new aws.ecs.TaskDefinition(`${name}-task`, {
                family: name,
                networkMode: "awsvpc",
                requiresCompatibilities: ["FARGATE"],
                cpu: "256",
                memory: "512",
                containerDefinitions: pulumi.interpolate`[{
                    "name": "${name}",
                    "image": "${args.containerImage}",
                    "portMappings": [{"containerPort": 80}],
                    "environment": [{"name": "ENV", "value": "${args.environment}"}]
                }]`,
            }, defaultParent);
    
            this.service = new aws.ecs.Service(`${name}-service`, {
                cluster: clusterName,
                taskDefinition: taskDefinition.arn,
                desiredCount: args.desiredCount || 1,
                launchType: "FARGATE",
                networkConfiguration: {
                    subnets: subnetIds,
                    assignPublicIp: true,
                },
            }, defaultParent);
    
            this.url = pulumi.interpolate`http://${this.loadBalancer.dnsName}`;
    
            this.registerOutputs({
                service: this.service,
                loadBalancer: this.loadBalancer,
                url: this.url,
            });
        }
    }
    
    // Usage: developers create WebApplications in Pulumi, without knowing they depend on Terraform-managed resources
    const app = new WebApplication("my-app", {
        terraformInfrastructure: infrastructureState,
        containerImage: "nginx:latest",
        environment: "production",
        desiredCount: 3,
    });
    

    Learning resources

    Documentation and guides

    Community resources

    Case studies and real-world implementations

    Getting help and Contributing to Pulumi

    Reach out to us via these support channels:

    Open source contributions

    We always welcome contributions, especially from our more advanced users who have practical experience building with both Terraform and Pulumi. Here are some ways you can contribute to the Pulumi ecosystem:

    Community engagement


    Have questions or feedback about this guide? Join us in the Pulumi Community Slack or open an issue on GitHub.

      Guide to Secure Infrastructure Automation