The Pulumi Terraform Bridge lets you use any of the thousands of Terraform providers directly within your Pulumi programs, effectively giving you access to a massive ecosystem of infrastructure resources managed by a familiar tool.

Let’s spin up a simple example. Imagine we want to deploy an AWS S3 bucket and a Cloudflare DNS record pointing to it. Normally, this would involve separate Terraform configurations or Pulumi resources for each provider. With the bridge, we can do it all in one Pulumi program written in TypeScript.

First, you’ll need to install the necessary Pulumi providers. In your Pulumi project, you’d run:

pulumi plugin install resource aws v6.0.0
pulumi plugin install resource cloudflare v4.0.0

Now, here’s the Pulumi code that uses both the AWS and Cloudflare Terraform providers:

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
import * as cloudflare from "@pulumi/cloudflare";
import * as terraform from "@pulumi/terraform";

// Configure AWS provider (assuming you have AWS credentials configured)
const awsProvider = new terraform.Provider("aws-provider", {
    // The 'aws' provider is implicitly registered with Pulumi
    // by the @pulumi/aws package, but we can explicitly configure
    // it here if needed, or for using specific versions.
    // For simplicity, we'll rely on the default configuration.
});

// Configure Cloudflare provider (assuming you have Cloudflare credentials configured)
const cloudflareProvider = new terraform.Provider("cloudflare-provider", {
    // Similar to AWS, the 'cloudflare' provider is often implicitly handled.
    // Explicit configuration is possible if required.
});

// Use the AWS S3 bucket resource from the Terraform AWS provider
const s3Bucket = new terraform.RemoteStateReference("aws-bucket-tf", {
    // This is a placeholder for how you'd reference an existing Terraform state.
    // For creating a new resource, you'd use a direct resource type.
    // Let's create a new bucket directly using the Terraform provider resource.
    // The `terraform.Provider` instance above is used to implicitly pass configuration
    // to the underlying Terraform providers when using their resources.
});

// Example: Creating a new S3 bucket using the Terraform AWS provider resource
const myBucket = new aws.s3.Bucket("my-bucket-tf", {
    bucket: "my-pulumi-tf-bridge-bucket-12345", // Replace with a unique name
    tags: {
        ManagedBy: "PulumiTerraformBridge",
    }
}, { provider: awsProvider }); // Explicitly associate with the configured provider

// Use the Cloudflare DNS record resource from the Terraform Cloudflare provider
const dnsRecord = new cloudflare.Record("dns-record-tf", {
    zoneId: "YOUR_CLOUDFLARE_ZONE_ID", // Replace with your Cloudflare Zone ID
    name: "app.example.com",
    value: myBucket.bucketDomainName, // Reference the S3 bucket's domain name
    type: "CNAME",
    ttl: 300,
}, { provider: cloudflareProvider }); // Explicitly associate with the configured provider

export const bucketName = myBucket.id;
export const dnsRecordName = dnsRecord.hostname;

In this code:

  • We import the @pulumi/aws and @pulumi/cloudflare packages. Crucially, these packages don’t just provide Pulumi-native resources; they also expose the underlying Terraform provider resources under a terraform namespace if you look closely, or allow you to explicitly configure and use the Terraform providers via new terraform.Provider(...). The example above shows how to use the aws.s3.Bucket and cloudflare.Record resources, which are actually Pulumi’s wrappers around the respective Terraform provider resources.
  • We instantiate terraform.Provider for AWS and Cloudflare. This is how Pulumi knows to use the Terraform versions of these providers and how to pass configuration (like credentials, regions, etc.) to them.
  • We then declare aws.s3.Bucket and cloudflare.Record resources. When you use resources from these packages after configuring the terraform.Provider, Pulumi leverages the bridge to call into the Terraform providers.
  • The value of the Cloudflare DNS record dynamically references the bucketDomainName of the S3 bucket. Pulumi understands this dependency and ensures the bucket is created before the DNS record.

When you pulumi up this program, Pulumi will:

  1. Detect that you’re using resources that map to Terraform providers.
  2. Invoke the Terraform AWS provider to create the S3 bucket.
  3. Invoke the Terraform Cloudflare provider to create the DNS record, using the output from the S3 bucket creation.

This approach allows you to manage resources across different cloud providers and services using a single Pulumi program, leveraging the vast catalog of Terraform providers. You get the benefits of Pulumi’s programming model (languages, state management, component resources) combined with the breadth of Terraform’s provider ecosystem.

The most surprising thing is that the Pulumi resource types like aws.s3.Bucket you import from @pulumi/aws can, under the hood, be configured to use the Terraform provider implementation instead of a native Pulumi one, and this switch is often seamless thanks to the bridge. You’re not just calling external Terraform, you’re embedding the Terraform provider’s logic directly into your Pulumi program’s execution graph.

The core problem this solves is fragmentation. You might have existing Terraform modules, a need to manage a service only supported by a Terraform provider, or simply prefer the Pulumi programming model but want to tap into the Terraform ecosystem. The bridge acts as a universal adapter, making thousands of Terraform resources available as if they were native Pulumi resources, complete with cross-resource dependencies and output exports. You control the lifecycle, dependencies, and configuration of these resources through familiar Pulumi constructs.

A key detail often missed is how Pulumi handles the underlying Terraform provider execution. It’s not simply running terraform apply in a subprocess for each resource. Instead, Pulumi invokes the Terraform provider as a plugin directly within its execution engine. This allows for much tighter integration, enabling real-time dependency tracking and output interpolation between resources managed by different providers (Terraform or native Pulumi) within the same Pulumi program. You’re essentially extending Pulumi’s resource model with Terraform’s provider implementations.

The next concept to explore is how to manage dependencies and outputs between resources managed by native Pulumi providers and those managed by Terraform providers via the bridge.

Want structured learning?

Take the full Pulumi course →