1. Docs
  2. Pulumi IaC
  3. Get started
  4. Pulumi for Terraform Users
  5. Reference Terraform State

Reference Terraform State

    Why reference state?

    Rather than migrating your entire Terraform infrastructure to Pulumi, you can reference existing Terraform state files to access outputs and resource attributes. This enables coexistence where different teams can use their preferred tools while sharing infrastructure.

    Common scenarios include:

    • Operations teams manage core infrastructure with Terraform
    • Development teams deploy applications with Pulumi
    • Gradual adoption of Pulumi happens alongside existing Terraform workflows

    Referencing Existing Infrastructure State

    Here’s an example of a minimal Terraform config that centrally manages an AWS ECS cluster and ECR repository. Pulumi can reference these outputs programmatically.

    # An AWS ECS Cluster
    resource "aws_ecs_cluster" "cluster" {
      name = "app-cluster"
    }
    
    # An AWS ECR Repository
    resource "aws_ecr_repository" "repo" {
      name = "image-repo"
    }
    
    # Outputs that Pulumi will reference
    output "ecs_cluster_name" {
      value = aws_ecs_cluster.cluster.name
    }
    
    output "ecr_repository_url" {
      value = aws_ecr_repository.repo.repository_url
    }
    

    Reference local state files

    For Terraform workspaces using local state files, you can reference them directly, via the getLocalReference function:

    import * as terraform from "@pulumi/terraform";
    
    // Reference local Terraform state
    const tfState = terraform.state.getLocalReferenceOutput({
        path: "../infrastructure/terraform.tfstate",
    });
    
    // Access Terraform outputs
    const ecsClusterName = tfState.outputs["ecs_cluster_name"];
    const ecrRepository = tfState.outputs["ecr_repository_url"];
    
    import pulumi
    import pulumi_terraform as terraform
    
    # Reference local Terraform state
    tf_state = terraform.state.get_local_reference(
        path="../infrastructure/terraform.tfstate"
    )
    
    # Access Terraform outputs
    ecs_cluster_name = tf_state.outputs["ecs_cluster_name"]
    ecr_repository = tf_state.outputs["ecr_repository_url"]
    
    pulumi.export("clusterName", ecs_cluster_name)
    pulumi.export("repositoryUrl", ecr_repository)
    
    package main
    
    import (
    	"github.com/pulumi/pulumi-terraform/sdk/v6/go/terraform/state"
    	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
    )
    
    func main() {
    	pulumi.Run(func(ctx *pulumi.Context) error {
    		tfState := state.GetLocalReferenceOutput(ctx, state.GetLocalReferenceOutputArgs{
    			Path: pulumi.String("../infrastructure/terraform.tfstate"),
    		})
    
    		// Access Terraform outputs
    		ecsClusterName := tfState.Outputs().MapIndex(pulumi.String("ecs_cluster_name"))
    		ecrRepository := tfState.Outputs().MapIndex(pulumi.String("ecr_repository_url"))
    
    		ctx.Export("clusterName", ecsClusterName)
    		ctx.Export("repositoryUrl", ecrRepository)
    		return nil
    	})
    }
    
    using System.Collections.Generic;
    using Pulumi;
    using Pulumi.Terraform.State;
    
    return await Deployment.RunAsync(() =>
    {
        // Reference local Terraform state
        var tfState = GetLocalReference.Invoke(new GetLocalReferenceInvokeArgs
        {
            Path = "../infrastructure/terraform.tfstate",
        }).Apply(x => x.Outputs);
    
        // Access Terraform outputs
        var ecsClusterName = tfState.Apply(x => x["ecs_cluster_name"]);
        var ecrRepository = tfState.Apply(x => x["ecr_repository_url"]);
    
        return new Dictionary<string, object?>
        {
            ["clusterName"] = ecsClusterName,
            ["repositoryUrl"] = ecrRepository,
        };
    });
    
    package myproject;
    
    import com.pulumi.Pulumi;
    import com.pulumi.core.Output;
    import com.pulumi.terraform.state.StateFunctions;
    import com.pulumi.terraform.state.inputs.GetLocalReferenceArgs;
    
    public class App {
        public static void main(String[] args) {
            Pulumi.run(ctx -> {
                // Reference local Terraform state
                var tfState = StateFunctions.getLocalReference(
                    GetLocalReferenceArgs.builder()
                    .path("../infrstructure/terraform.tfstate")
                    .build()
                );
    
                // Access Terraform outputs
                var ecsClusterName = tfState.applyValue(x -> x.outputs().get("ecs_cluster_name"));
                var ecrRepository = tfState.applyValue(x -> x.outputs().get("ecr_repository_url"));
    
                ctx.export("clusterName", ecsClusterName);
                ctx.export("repositoryUrl", ecrRepository);
            });
        }
    }
    
    name: reference-terraform-state
    runtime: yaml
    description: Reference Terraform state from Pulumi
    variables:
      tfState:
        fn::invoke:
          function: terraform:state:getLocalReference
          arguments:
            path: ../terraform.tfstate
    outputs:
      clusterName: ${tfState.outputs["ecs_cluster_name"]}
      repositoryUrl: ${tfState.outputs["ecr_repository_url"]}
    

    Reference remote state

    For production environments, many prefer to store thier state in Terraform Cloud. To reference remote state, use the getRemoteReference function:

    import * as terraform from "@pulumi/terraform";
    
    // Reference remote Terraform state
    const tfState = terraform.state.getRemoteReferenceOutput({
        token: "<secret-token>",
        organization: "my-org",
        workspaces: {
            prefix: "dev",
        },
    });
    
    // Access Terraform outputs
    const ecsClusterName = tfState.outputs["ecs_cluster_name"];
    const ecrRepository = tfState.outputs["ecr_repository_url"];
    
    import pulumi
    import pulumi_terraform as terraform
    
    # Reference remote Terraform state
    tf_state = terraform.state.get_remote_reference_output(
        token="<secret-token>",
        organization="my-org",
        workspaces={
            "prefix": "dev",
        }
    )
    
    # Access Terraform outputs
    ecs_cluster_name = tf_state.outputs["ecs_cluster_name"]
    ecr_repository = tf_state.outputs["ecr_repository_url"]
    
    pulumi.export("clusterName", ecs_cluster_name)
    pulumi.export("repositoryUrl", ecr_repository)
    
    package main
    
    import (
    	"github.com/pulumi/pulumi-terraform/sdk/v6/go/terraform/state"
    	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
    )
    
    func main() {
    	pulumi.Run(func(ctx *pulumi.Context) error {
    		organization := "my-org"
    		workspacesPrefiix := "dev"
    		token := "<secret-token>"
    
            // Reference remote Terraform state
    		tfState := state.GetRemoteReferenceOutput(ctx, state.GetRemoteReferenceOutputArgs{
    			Organization: pulumi.String(organization),
    			Token:        pulumi.String(token),
    			Workspaces: state.WorkspacesArgs{
    				Prefix: pulumi.StringPtr(workspacesPrefiix),
    			},
    		})
    
    		// Access Terraform outputs
    		ecsClusterName := tfState.Outputs().MapIndex(pulumi.String("ecs_cluster_name"))
    		ecrRepository := tfState.Outputs().MapIndex(pulumi.String("ecr_repository_url"))
    
    		ctx.Export("clusterName", ecsClusterName)
    		ctx.Export("repositoryUrl", ecrRepository)
    		return nil
    	})
    }
    
    using System.Collections.Generic;
    using Pulumi;
    using Pulumi.Terraform.State;
    
    return await Deployment.RunAsync(() =>
    {
        // Reference remote Terraform state
        var tfState = GetRemoteReference.Invoke(new GetRemoteReferenceInvokeArgs
        {
            Organization = "my-org",
    	    Token = "<secret-token>>",
    	    Workspaces = new Pulumi.Terraform.State.Inputs.WorkspacesArgs{
    		    Prefix="dev"
    	    }
        }).Apply(x => x.Outputs);
    
        // Access Terraform outputs
        var ecsClusterName = tfState.Apply(x => x["ecs_cluster_name"]);
        var ecrRepository = tfState.Apply(x => x["ecr_repository_url"]);
    
        return new Dictionary<string, object?>
        {
            ["clusterName"] = ecsClusterName,
            ["repositoryUrl"] = ecrRepository,
        };
    });
    
    package myproject;
    
    import com.pulumi.Pulumi;
    import com.pulumi.core.Output;
    import com.pulumi.terraform.state.StateFunctions;
    import com.pulumi.terraform.state.inputs.GetRemoteReferenceArgs;
    import com.pulumi.terraform.state.inputs.WorkspacesArgs;
    
    public class App {
        public static void main(String[] args) {
            Pulumi.run(ctx -> {
                // Reference remote Terraform state
                var tfState = StateFunctions.getRemoteReference(
                    GetRemoteReferenceArgs.builder()
                    .organization("my-org")
            		.token("<secret-token>")
    		        .workspaces(
    		            WorkspacesArgs.builder()
    		            .prefix("together-guide")
                        .build()
    		        )
                    .build()
                );
    
                // Access Terraform outputs
                var ecsClusterName = tfState.applyValue(x -> x.outputs().get("ecs_cluster_name"));
                var ecrRepository = tfState.applyValue(x -> x.outputs().get("ecr_repository_url"));
    
                ctx.export("clusterName", ecsClusterName);
                ctx.export("repositoryUrl", ecrRepository);
            });
        }
    }
    
    name: terraform-remote-state-with-yaml
    runtime: yaml
    variables:
      tfState:
        fn::invoke:
          function: terraform:state:getRemoteReference
          arguments:
            organization: my-org
            token:
              fn::secret: ${remote_tf_token} # A secret value containing the Terraform API token
            workspaces:
              prefix: dev
    outputs:
      clusterName: ${tfState.outputs["ecs_cluster_name"]}
      repositoryUrl: ${tfState.outputs["ecr_repository_url"]}
    

    Example: Containerized application on ECS

    Let’s create a practical example where an internal platform engineering team uses Terraform to manage an ECS cluster and ECR repository, while the developer teams use Pulumi to manage containerized application deployments via Fargate.

    Terraform infrastructure (reference)

    The Terraform configuration for your company-wide ECS/ECR platform might look like this:

    # infrastructure/main.tf
    resource "aws_ecs_cluster" "cluster" {
      name = "app-cluster"
    }
    
    resource "aws_ecr_repository" "repo" {
      name = "app-container-repo"
    }
    
    # Outputs that Pulumi will reference
    output "ecs_cluster_arn" {
      value = aws_ecs_cluster.cluster.arn
    }
    
    output "ecr_repository_url" {
      value = aws_ecr_repository.repo.repository_url
    }
    

    Pulumi application deployment

    As a developer using this platform, we want to make a small containerized web app and deploy it.

    First, we need to make the app. It will consist of a directory called app/ holding a small index.html file, and a Dockerfile that defines an Nginx web server to host it.

    Example: app/index.html - The HTML page

    <html>
      <head><meta charset="UTF-8">
        <title>Hello Fargate</title>
      </head>
      <body>
          <p>I LOVE PULUMI!</p>
          <p>Made with ❤️ with <a href="https://pulumi.com">Pulumi</a></p>
      </body>
    </html>
    

    Example: app/Dockerfile - The Nginx proxy

    FROM nginx
    COPY index.html /usr/share/nginx/html
    

    Next, we’ll make a TypeScript Pulumi program that packages this up into a Docker image and runs the app on the company’s shared infrastructure.

    Example: index.ts - A Pulumi program that references the shared infrastructure

    import * as aws from "@pulumi/aws";
    import * as awsx from "@pulumi/awsx";
    import * as pulumi from "@pulumi/pulumi";
    import * as terraform from "@pulumi/terraform";
    
    // Reference local Terraform state
    const tfState = terraform.state.getLocalReferenceOutput({
        path: "../infrastructure/terraform.tfstate",
    });
    
    // Access Terraform outputs
    const clusterArn = tfState.outputs["ecs_cluster_arn"];
    const repoUrl = tfState.outputs["ecr_repository_url"];
    
    // Build and publish our application's container image from ./app to the ECR repository.
    const image = new awsx.ecr.Image("image", {
        repositoryUrl: repoUrl,
        context: "./app",
        platform: "linux/amd64",
    });
    
    // Create a load balancer to listen for requests and route them to the container.
    const loadbalancer = new awsx.lb.ApplicationLoadBalancer("loadbalancer", {});
    
    // Define the service and configure it to use our image and load balancer.
    const service = new awsx.ecs.FargateService("service", {
        cluster: clusterArn,
        assignPublicIp: true,
        taskDefinitionArgs: {
            container: {
                name: "awsx-ecs",
                image: image.imageUri,
                cpu: 128,
                memory: 512,
                essential: true,
                portMappings: [{
                    containerPort: 80,
                    targetGroup: loadbalancer.defaultTargetGroup,
                }],
            },
        },
    });
    
    // Export the URL so we can easily access it.
    export const frontendURL = pulumi.interpolate `http://${loadbalancer.loadBalancer.dnsName}`;
    

    Our Pulumi program references a local Terraform state file to get the important details (the ECS ARN, the ECR URL), packages up the application into a Docker image, pushes that image to the ECR repo, creates a load balancer, and then defines a Fargate service that uses all of the above to run the containerized app. Finally, we see the deployed URL as a Pulumi output, and can immediately see that the app is running and serving up the HTML file.

    This example demonstrates how Pulumi can seamlessly consume Terraform outputs to deploy applications on existing shared infrastructure.

    Best practices

    1. Use consistent naming: Ensure Terraform output names are descriptive and stable
    2. Secure state access: Use Terraform Cloud to control access to state files
    3. Document dependencies: Clearly document which Pulumi stacks depend on which Terraform states
    4. Monitor state changes: Be aware that changes to Terraform outputs will affect dependent Pulumi stacks
      Guide to Secure Infrastructure Automation