Use a Terraform Module in Pulumi
This guide will walk you through the process of using existing Terraform modules directly in your Pulumi programs, allowing you to leverage the vast Terraform module ecosystem.
Prerequisites:
- The Pulumi CLI
- One of Pulumi’s supported language runtimes installed
- Access to a cloud provider account for deployment (e.g., AWS, Azure, Google Cloud)
Why Use Terraform Modules in Pulumi?
Terraform has a mature ecosystem with thousands of modules available in the Terraform Registry. These modules encapsulate well-tested infrastructure patterns that you might want to leverage in your Pulumi projects without having to rewrite them.
Also, many Terraform users have created their own custom modules and would like to avoid re-writing them in order to use Pulumi. Using Terraform modules directly within Pulumi allows you to use Terraform and Pulumi side-by-side, enabling the two technologies to coexist, taking a slower gentler migration path from Terraform to Pulumi. This is especially powerful in larger organizations, where some teams prefer Pulumi’s workflows, and others prefer to continue using Terraform.
Key benefits:
- Leverage Existing Modules: Use the rich ecosystem of Terraform modules directly in Pulumi.
- Migrate Gradually: Incrementally migrate from Terraform to Pulumi without rewriting everything at once.
- Consistency: Maintain consistency across teams that may be using a mix of Terraform and Pulumi.
How It Works
The Any HCL Module package allows you to consume Terraform modules as if they were native Pulumi packages. It works by:
- Automatically installing and managing OpenTofu (an open-source Terraform-compatible implementation) to execute the module.
- Passing the inputs you provide to the module as Terraform variables.
- Managing state through your standard Pulumi state backend.
- Exposing module outputs as native Pulumi outputs.
Getting Started
Adding a Terraform Module to Your Pulumi Project
To use a Terraform module in Pulumi, first add it to your project using the pulumi package add command:
pulumi package add hcl module <module-source> [<version>]
Where:
<module-source>is either a registry module identifier (e.g.terraform-aws-modules/rds/aws) or a local path<version>is an optional version constraint (e.g.3.5.0)
For example, to add the AWS S3 bucket module from the Terraform Registry:
pulumi package add hcl module terraform-aws-modules/s3-bucket/aws 4.1.2
This will generate a local SDK in your programming language that you can import into your Pulumi program.
Using a Local Terraform Module
You can also use local Terraform modules:
pulumi package add hcl module ./path/to/module
Any directory containing .tf files and optionally variables.tf and outputs.tf is considered a valid module.
Using a module from Pulumi Cloud
If your organization publishes Terraform modules to the Pulumi Cloud registry, every published version is converted into a Pulumi package for you. Install it by package name, which is the module’s name and system joined with a hyphen. The system is the last segment of the module’s address, naming what the module provisions, such as aws or azurerm:
pulumi package add <name>-<system>[@<version>]
A module published as acme-corp/vpc/aws installs as vpc-aws. This is the same as any other Pulumi package: you get a generated SDK in your language, an API reference on the package’s page, and usage tracking showing which of your stacks depend on it and which are behind the latest version. Installing a converted package requires Pulumi CLI 3.248.0 or newer; see Download & Install Pulumi to upgrade.
The package’s page in Pulumi Cloud shows whether a given version has converted.
See Terraform Modules in the Pulumi Cloud Registry for the publishing side and the broader module workflow.
Example: Using the AWS S3 Bucket Module
Here’s an example of how to use the AWS S3 bucket module to create a bucket in your Pulumi program.
First, add the module to your project:
$ pulumi package add hcl module terraform-aws-modules/s3-bucket/aws 4.1.2
After adding the package, your Pulumi.yaml is updated with the module definition:
Example: Pulumi.yaml
name: s3-bucket-example
runtime:
name: nodejs
options:
packagemanager: npm
packages:
s3-bucket:
source: hcl
version: 0.13.0
parameters:
- module
- terraform-aws-modules/s3-bucket/aws
- 4.1.2
Since this was a TypeScript project, Pulumi generated a TypeScript SDK for the module, making it available as @pulumi/s3-bucket. We can now use the Terraform module directly in our code:
Example: index.ts
import * as s3Bucket from "@pulumi/s3-bucket";
// Create an S3 bucket using the Terraform module
const bucket = new s3Bucket.Module("my-bucket", {
bucketPrefix: "my-example-bucket",
tags: {
Environment: "dev",
},
});
// The module's outputs are strongly typed
export const bucketArn = bucket.s3BucketArn;
export const bucketId = bucket.s3BucketId;
Since this was a Python project, Pulumi generated a Python SDK for the module, making it available as pulumi_s3_bucket. We can now use the Terraform module directly in our code:
Example: __main__.py
import pulumi
import pulumi_s3_bucket as s3bucket
# Create an S3 bucket using the Terraform module
bucket = s3bucket.Module("my-bucket",
bucket_prefix="my-example-bucket",
tags={
"Environment": "dev",
})
# The module's outputs are strongly typed
pulumi.export("bucket_arn", bucket.s3_bucket_arn)
pulumi.export("bucket_id", bucket.s3_bucket_id)
Since this was a Go project, Pulumi generated a Go SDK for the module. We can now use the Terraform module directly in our code:
Example: main.go
package main
import (
"example.com/pulumi-s3-bucket/sdk/go/s3bucket"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
// Create an S3 bucket using the Terraform module
bucket, err := s3bucket.NewModule(ctx, "my-bucket", &s3bucket.ModuleArgs{
BucketPrefix: pulumi.String("my-example-bucket"),
Tags: pulumi.StringMap{
"Environment": pulumi.String("dev"),
},
})
if err != nil {
return err
}
// The module's outputs are strongly typed
ctx.Export("bucketArn", bucket.S3BucketArn)
ctx.Export("bucketId", bucket.S3BucketId)
return nil
})
}
Since this was a C# project, Pulumi generated a C# SDK for the module, making it available as Pulumi.S3Bucket. We can now use the Terraform module directly in our code:
Example: Program.cs
using System.Collections.Generic;
using Pulumi;
using Pulumi.S3Bucket;
return await Deployment.RunAsync(() =>
{
// Create an S3 bucket using the Terraform module
var bucket = new Module("my-bucket", new ModuleArgs
{
BucketPrefix = "my-example-bucket",
Tags =
{
{ "Environment", "dev" },
},
});
// The module's outputs are strongly typed
return new Dictionary<string, object?>
{
["bucketArn"] = bucket.S3BucketArn,
["bucketId"] = bucket.S3BucketId,
};
});
Since this was a Java project, Pulumi generated a Java SDK for the module, making it available as com.pulumi.s3bucket. We can now use the Terraform module directly in our code:
Example: App.java
package myproject;
import com.pulumi.Pulumi;
import com.pulumi.s3bucket.Module;
import com.pulumi.s3bucket.ModuleArgs;
import java.util.Map;
public class App {
public static void main(String[] args) {
Pulumi.run(ctx -> {
// Create an S3 bucket using the Terraform module
var bucket = new Module("my-bucket", ModuleArgs.builder()
.bucketPrefix("my-example-bucket")
.tags(Map.of("Environment", "dev"))
.build());
// The module's outputs are strongly typed
ctx.export("bucketArn", bucket.s3BucketArn());
ctx.export("bucketId", bucket.s3BucketId());
});
}
}
When authoring in YAML, there’s no SDK to generate — you reference the module by its schema token, which takes the format <package-name>:index:Module:
Example: Pulumi.yaml
name: s3-bucket-example
runtime: yaml
description: Use a Terraform module in Pulumi
resources:
# Create an S3 bucket using the Terraform module
my-bucket:
type: s3-bucket:index:Module
properties:
bucketPrefix: my-example-bucket
tags:
Environment: dev
# The module's outputs are strongly typed
outputs:
bucketArn: ${my-bucket.s3BucketArn}
bucketId: ${my-bucket.s3BucketId}
packages:
s3-bucket:
source: hcl
version: 0.13.0
parameters:
- module
- terraform-aws-modules/s3-bucket/aws
- 4.1.2
In the above code, the imported Terraform module works the same as any other Pulumi code. Outputs are returned, and resource state is stored in your Pulumi state storage, alongside all your other Pulumi-native resources. This also means that resource dependencies work as expected between Pulumi-native resources and resources created by Terraform modules.
Configuring Terraform Providers
A Terraform module runs against your project’s default provider configuration — the same configuration your Pulumi-native resources use — so it inherits whatever region, account, or credentials you’ve already set. To target a specific region, for example, set it the way you would for any Pulumi provider:
$ pulumi config set aws:region us-west-2
You can also supply provider settings, including short-lived credentials, through a Pulumi ESC environment. See Providers for the full range of configuration options.
Troubleshooting
Fixing Invalid Relative Paths
When a module accepts a file path, pass an absolute path instead of a relative one. For example, the AWS Lambda module accepts a source_path that points to your function’s code:
import * as hcl from "@pulumi/hcl";
const lambdaModule = new hcl.Module("my-lambda", {
source: "terraform-aws-modules/lambda/aws",
inputs: {
function_name: "my-function",
handler: "index.handler",
runtime: "nodejs20.x",
source_path: `${process.cwd()}/src`,
},
});
This is necessary because Terraform modules run from a different working directory than your Pulumi program, so a relative path would resolve incorrectly. The example above uses the Node.js built-in process.cwd() to anchor the path to the function code in src to your project’s working directory.