releases.sh

Terraform state backend, module registry, HCL support in Pulumi Cloud

August 4, 2026Pulumi BlogView original ↗
3 featuresThis release3 featuresNew capabilitiesAI-tallied from the release notes
From the original release noteView original ↗

Today’s big release contains a whole new set of features designed for seamless interoperability with the Terraform and OpenTofu ecosystems, and there’s a lot there — so much that it can be tough to get your head around all of it. But it generally falls into three major categories:

  • Support for Pulumi Cloud as a Terraform state backend, including remote execution with human approvals
  • A Terraform module registry in Pulumi Cloud that lets you publish, document, and share your modules even across language boundaries
  • First-class support for HCL as an authoring language in the Pulumi engine

To make this release a little easier to appreciate holistically, I’ve put together a quick end-to-end walkthrough that doesn’t quite cover everything, but does cover the big stuff, and should give you a sense of how it all comes together. We’ll start with a simple Terraform project that you’ll deploy to AWS, and then one step at a time, bring it into Pulumi Cloud and kick the tires on each of these new features as we go. It’ll take a bit, but all you’ll need are a free Pulumi account and the ability to deploy an S3 bucket to AWS.

We’ve got a bunch to cover, so let’s jump right in.

Start with a Terraform project

Our tour begins with a tiny Terraform project that provisions a single Amazon S3 bucket using a locally defined module that we’ll publish later. The project is available on GitHub as a template, and the easiest way to use it is with the GitHub CLI:

<span class="line"><span class="cl">$ gh repo create my-tf-project <span class="se">\
</span></span></span><span class="line"><span class="cl"> --template cnunciato/simple-tf-template <span class="se">\
</span></span></span><span class="line"><span class="cl"> --public <span class="se">\
</span></span></span><span class="line"><span class="cl"> --clone <span class="o">&&</span> <span class="nb">cd</span> my-tf-project
</span></span>

We’ll use the local Terraform backend to start. Set your AWS credentials (preferably with environment variables), then deploy the project with Terraform or OpenTofu. (This walkthrough uses the terraform CLI, but you can swap in tofu if that’s your preference.)

<span class="line"><span class="cl">$ terraform init <span class="o">&&</span> terraform apply
</span></span><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Apply complete! Resources: <span class="m">2</span> added, <span class="m">0</span> changed, <span class="m">0</span> destroyed.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Outputs:
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">bucket_arn</span> <span class="o">=</span> <span class="s2">"arn:aws:s3:::my-tf-project-bucket-14d19ece"</span>
</span></span><span class="line"><span class="cl"><span class="nv">bucket_name</span> <span class="o">=</span> <span class="s2">"my-tf-project-bucket-14d19ece"</span>
</span></span>

Now let’s see how to move this project into Pulumi Cloud.

Migrate the state to Pulumi Cloud

First, create a Pulumi Cloud account if you don’t already have one (it’s free for individuals) and sign in to the Pulumi console. Then, add a backend block to main.tf, swapping <your-org> for your own Pulumi Cloud account or organization name:

<span class="line"><span class="cl"><span class="k">terraform</span> {<span class="c1">
</span></span></span><span class="line"><span class="cl"><span class="c1"> # ...
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="k">backend</span> <span class="s2">"remote"</span> {
</span></span><span class="line"><span class="cl"><span class="n"> hostname</span> <span class="o">=</span> <span class="s2">"tf.pulumi.com"</span>
</span></span><span class="line"><span class="cl"><span class="n"> organization</span> <span class="o">=</span> <span class="s2">"<your-org>"</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="k">workspaces</span> {
</span></span><span class="line"><span class="cl"><span class="n"> name</span> <span class="o">=</span> <span class="s2">"my-tf-project_dev"</span>
</span></span><span class="line"><span class="cl"> }
</span></span><span class="line"><span class="cl"> }
</span></span><span class="line"><span class="cl">}
</span></span>

The workspace name is an underscore-delimited string that expresses the name of the project you’d like to use (here, my-tf-project) and the stack (dev). A single Pulumi project can have as many stacks as you like.

Next, sign in to Pulumi Cloud with the Terraform CLI:

<span class="line"><span class="cl">$ terraform login tf.pulumi.com
</span></span>

Choose yes when prompted, and you’ll be taken to Pulumi Cloud to create a personal access token, which you can paste into the prompt to authenticate:

Creating a personal access token in the Pulumi Cloud console

Success! Logged in to Terraform Enterprise (tf.pulumi.com)

With the backend block in place and your terraform CLI signed in to Pulumi Cloud, you’re ready to complete the migration:

<span class="line"><span class="cl">$ terraform init -migrate-state
</span></span>

Terraform should detect the backend change and offer to copy your existing state:

Do you want to copy existing state to the new backend?
Pre-existing state was found while migrating the previous "local" backend
to the newly configured "remote" backend. [...] Enter "yes" to copy and
"no" to start with an empty state.
Enter a value: yes

Choose yes, and you’re done:

Successfully configured the backend "remote"! Terraform will automatically
use this backend unless the backend configuration changes.

Note that nothing about your deployed infrastructure has changed here; all we did was migrate your local state to Pulumi Cloud, and the process is identical whether you’re moving from S3, Azure, Google Cloud, or HCP Terraform or Terraform Enterprise. See Store Terraform state in Pulumi Cloud for details.

Now hop over to the Pulumi Cloud console, choose Stacks, and you’ll see your new stack in the list, along with its first update:

The migrated Terraform stack and its first update in the Pulumi Cloud console

Runs happen remotely by default

Another thing to note is that Terraform stacks backed by Pulumi Cloud run remotely by default, just as they do in HCP Terraform and Terraform Enterprise. Plans and applies are executed on a Pulumi Cloud-hosted runner, and output is streamed simultaneously to your terminal and to the browser. Try making a small change to main.tf — adding a tag, say — and run terraform apply:

<span class="line"><span class="cl"> tags = {
</span></span><span class="line"><span class="cl"> Environment = "dev"
</span></span><span class="line"><span class="cl"><span class="gi">+ Owner = "me"
</span></span></span><span class="line"><span class="cl"> }
</span></span>
<span class="line"><span class="cl">$ terraform apply
</span></span><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Running apply in the remote backend. Output will stream here.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">To view this run in a browser, visit:
</span></span><span class="line"><span class="cl">https://tf.pulumi.com/app/cnunciato/my-tf-project_dev/runs/run-8a635542-334f-4367-b188-8b4b442b2550
</span></span><span class="line"><span class="cl">...
</span></span>

Confirm with another yes, and you’ll notice the apply fails — which makes sense, considering the runner doesn’t have your AWS credentials yet. That’s where Pulumi ESC comes in.

Configure cloud credentials with ESC

When you create a new Terraform stack, Pulumi Cloud automatically creates a linked ESC environment for you with the same name. You can use this environment to configure settings of all kinds, including cloud credentials and other encrypted secrets, and make those settings available to the stack at runtime.

In the stack’s Overview tab, click the linked my-tf-project/dev environment to open the environment editor, where you can either set your AWS credentials directly or wire up a new connection by choosing Add newLogin provider/configuration and following the steps for AWS.

Since I’ve already configured an environment for sharing my AWS credentials across all of my stacks (which I’ve named default/personal), I can just import that environment into this one to use it:

<span class="line"><span class="cl"><span class="nt">imports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl">- <span class="l">default/personal</span><span class="w">
</span></span></span>

Try this yourself, then run the apply again, and see that it succeeds:

<span class="line"><span class="cl">$ terraform apply -auto-approve
</span></span><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">OpenTofu will perform the following actions:
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="c1"># module.s3-bucket.aws_s3_bucket.this will be updated in-place</span>
</span></span><span class="line"><span class="cl"> ~ resource <span class="s2">"aws_s3_bucket"</span> <span class="s2">"this"</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl"> <span class="nv">id</span> <span class="o">=</span> <span class="s2">"my-tf-project-bucket-b64b8e37"</span>
</span></span><span class="line"><span class="cl"> ~ <span class="nv">tags</span> <span class="o">=</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl"> <span class="s2">"Environment"</span> <span class="o">=</span> <span class="s2">"dev"</span>
</span></span><span class="line"><span class="cl"> + <span class="s2">"Owner"</span> <span class="o">=</span> <span class="s2">"me"</span>
</span></span><span class="line"><span class="cl"> <span class="o">}</span>
</span></span><span class="line"><span class="cl"> ~ <span class="nv">tags_all</span> <span class="o">=</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl"> + <span class="s2">"Owner"</span> <span class="o">=</span> <span class="s2">"me"</span>
</span></span><span class="line"><span class="cl"> <span class="o">}</span>
</span></span><span class="line"><span class="cl"> <span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Plan: <span class="m">0</span> to add, <span class="m">1</span> to change, <span class="m">0</span> to destroy.
</span></span><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Apply complete! Resources: <span class="m">0</span> added, <span class="m">1</span> changed, <span class="m">0</span> destroyed.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Outputs:
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">bucket_arn</span> <span class="o">=</span> <span class="s2">"arn:aws:s3:::my-tf-project-bucket-b64b8e37"</span>
</span></span><span class="line"><span class="cl"><span class="nv">bucket_name</span> <span class="o">=</span> <span class="s2">"my-tf-project-bucket-b64b8e37"</span>
</span></span>

Remote execution is powered by Pulumi Deployments, which means your Terraform stacks can also be triggered by VCS events — pull requests and pushes to GitHub, GitLab, and others — as well as manual human approvals. Wiring that up is pretty simple as well:

  1. Under Management → Version Control in the Pulumi console, configure your VCS provider and add the my-tf-project repository.
  2. In your stack’s Settings tab, choose Deploy, configure the repository and the base branch to build from (e.g., main), and save.
  3. Push a commit to that branch.

When you do that, you’ll see a new plan in the Deployments tab, and once that finishes, you’ll be prompted to approve or decline the run:

A deployment run awaiting confirmation in the Pulumi Cloud console

Click Confirm, and you’re off and running.

And that’s it! Your Terraform stacks are now first-class citizens in Pulumi Cloud, with access control, Neo code reviews, and Pulumi Policies all available to them. Audit policies are runnable on any Terraform stack, and preventative policies can be used to block non-compliant changes before they happen.

Next up: modules.

Publish a Terraform module

If your team’s been using Terraform for a while, chances are you’ve also written some Terraform modules, so you’ll need somewhere to keep them as you move to Pulumi Cloud. In addition to its role as a Terraform state backend, Pulumi Cloud now includes a private registry that can host your Terraform modules and make them available across your organization.

Module publishing requires an Enterprise or Business Critical plan, so you’ll need an organization with one of those for this part — but you can easily create one with a free trial if you don’t have one already. In the Pulumi console, open the organization menu, choose Create organization, give it a name, and you’re good to go.

Pulumi Cloud’s Terraform registry API is wire-compatible with HCP Terraform’s, which means the tools you already use for publishing — e.g., the go-tfe library or the hashicorp/tfe Terraform provider — need only be pointed at tf.pulumi.com. To make this easy, our project includes a small Go program that uses go-tfe to do just that. Set a Pulumi access token in your terminal (you may need to obtain a new one), then run it from the project root, passing your organization name:

<span class="line"><span class="cl">$ <span class="nb">export</span> <span class="nv">PULUMI_ACCESS_TOKEN</span><span class="o">=</span><your-access-token>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Install Go at https://go.dev/doc/install if you don't have it already.</span>
</span></span><span class="line"><span class="cl">$ go -C upload-module run . <span class="se">\
</span></span></span><span class="line"><span class="cl"> -org <your-org> <span class="se">\
</span></span></span><span class="line"><span class="cl"> -provider aws <span class="se">\
</span></span></span><span class="line"><span class="cl"> -version 0.1.0 <span class="se">\
</span></span></span><span class="line"><span class="cl"> -path ./modules/s3-bucket
</span></span>

You should see a confirmation:

creating module veridian/s3-bucket/aws
uploaded veridian/s3-bucket/aws@0.1.0

Then, in the Pulumi console, navigate to Platform → Private components, and you should see your newly uploaded module in the list. Click into it to see its details, available versions, usage instructions, and API docs:

The published Terraform module’s detail page in the Pulumi Cloud registry

With the module now hosted in Pulumi Cloud, you can update your project to use the hosted version instead. Delete the local ./modules folder entirely, and then in main.tf, update the source and add a version, replacing <your-org> with your chosen org name:

<span class="line"><span class="cl"><span class="k">module</span> <span class="s2">"s3-bucket"</span> {
</span></span><span class="line"><span class="cl"><span class="n"> source</span> <span class="o">=</span> <span class="s2">"tf.pulumi.com/<your-org>/s3-bucket/aws"</span>
</span></span><span class="line"><span class="cl"><span class="n"> version</span> <span class="o">=</span> <span class="s2">"0.1.0"</span><span class="c1">
</span></span></span><span class="line"><span class="cl"><span class="c1">
</span></span></span><span class="line"><span class="cl"><span class="c1"> # ...
</span></span></span><span class="line"><span class="cl">}
</span></span>

Now run terraform plan and see that the module is downloaded and the plan, as expected, produces no changes:

<span class="line"><span class="cl">$ terraform plan
</span></span><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Initializing modules...
</span></span><span class="line"><span class="cl">Downloading tf.pulumi.com/veridian/s3-bucket/aws 0.1.0 <span class="k">for</span> s3-bucket...
</span></span><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">No changes. Your infrastructure matches the configuration.
</span></span>

Now let’s have a look at how you can use this module in other ways.

Consume the module from a Pulumi program

The whole point of a shared module registry is to help you get more mileage out of the work you put into your Terraform modules. When you use Pulumi Cloud for that registry, you can consume those modules not just from a Terraform program, but from any Pulumi program as well — and in any supported language.

Let’s see how this looks with a TypeScript project. If you haven’t already, install Pulumi, then run:

<span class="line"><span class="cl">$ mkdir ../my-typescript-project <span class="o">&&</span> <span class="nb">cd</span> <span class="nv">$_</span>
</span></span><span class="line"><span class="cl">$ pulumi new typescript
</span></span>

Follow the prompts, accepting the defaults. Then, add the Terraform module to your TypeScript project with pulumi package add:

<span class="line"><span class="cl">$ pulumi package add s3-bucket-aws
</span></span>

This fairly magical command generates a local TypeScript SDK at ./sdks that wraps the module as a Pulumi component. To use it, open up index.ts and replace its contents with the following:

<span class="line"><span class="cl"><span class="kr">import</span> <span class="o">*</span> <span class="kr">as</span> <span class="nx">bucket</span> <span class="kr">from</span> <span class="s2">"./sdks/s3-bucket"</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">myModule</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">bucket</span><span class="p">.</span><span class="nx">Module</span><span class="p">(</span><span class="s2">"my-module"</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">bucketPrefix</span><span class="o">:</span> <span class="s2">"my-new-bucket"</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">tags</span><span class="o">:</span> <span class="p">{},</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="kr">const</span> <span class="p">{</span> <span class="nx">bucketName</span><span class="p">,</span> <span class="nx">bucketArn</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">myModule</span><span class="p">;</span>
</span></span>

Configure your AWS credentials locally as before — or alternatively, use the ESC environment you created earlier if you set one up — then run pulumi up to deploy in the usual way:

<span class="line"><span class="cl">$ pulumi up
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Updating <span class="o">(</span>dev<span class="o">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> Type Name Status
</span></span><span class="line"><span class="cl"> + pulumi:pulumi:Stack my-typescript-project-dev created <span class="o">(</span>8s<span class="o">)</span>
</span></span><span class="line"><span class="cl"> + └─ s3-bucket:index:Module my-module created <span class="o">(</span>5s<span class="o">)</span>
</span></span><span class="line"><span class="cl"> + ├─ random:index:Id my-module-suffix created <span class="o">(</span>0.21s<span class="o">)</span>
</span></span><span class="line"><span class="cl"> + └─ aws:index:S3Bucket my-module-this created <span class="o">(</span>2s<span class="o">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Outputs:
</span></span><span class="line"><span class="cl"> bucketArn : <span class="s2">"arn:aws:s3:::my-new-bucket-bbfa52db"</span>
</span></span><span class="line"><span class="cl"> bucketName: <span class="s2">"my-new-bucket-bbfa52db"</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Resources:
</span></span><span class="line"><span class="cl"> + <span class="m">4</span> created
</span></span>

Having a language-specific, locally managed SDK at hand comes with many benefits, including IDE support, typed inputs and outputs, and more — but there may be times when you’ll prefer a simpler or more dynamic option. In these situations, you can instead choose to load the module at runtime.

In TypeScript, you’d do that with the @pulumi/hcl module. Try that now by installing it in your project:

<span class="line"><span class="cl">$ npm install @pulumi/hcl
</span></span>

… and using it in your program:

<span class="line"><span class="cl"><span class="kr">import</span> <span class="o">*</span> <span class="kr">as</span> <span class="nx">hcl</span> <span class="kr">from</span> <span class="s2">"@pulumi/hcl"</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">myModule</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">hcl</span><span class="p">.</span><span class="nx">Module</span><span class="p">(</span><span class="s2">"my-module"</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">source</span><span class="o">:</span> <span class="s2">"tf.pulumi.com/<your-org>/s3-bucket/aws"</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">version</span><span class="o">:</span> <span class="s2">"0.1.0"</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">inputs</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">bucket_prefix</span><span class="o">:</span> <span class="s2">"my-new-bucket"</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">tags</span><span class="o">:</span> <span class="p">{},</span>
</span></span><span class="line"><span class="cl"> <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="kr">const</span> <span class="p">{</span> <span class="nx">bucket_name</span><span class="p">,</span> <span class="nx">bucket_arn</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">myModule</span><span class="p">.</span><span class="nx">outputs</span><span class="p">;</span>
</span></span>

Because this approach uses untyped references, you’ll trade a little type safety for flexibility — but either way, you’re able to keep using the modules you’ve already built, while migrating to new infrastructure in general-purpose languages on whatever schedule works best for your team.

Write and run HCL, natively

For as much flexibility as general-purpose languages offer, some teams simply prefer to use HCL. So as of today, HCL is now a first-class language in the Pulumi engine, right alongside TypeScript, Python, Go, C#, Java, and YAML.

The easiest way to get a feel for it is to create a new project from a template:

<span class="line"><span class="cl">$ mkdir ../my-hcl-project <span class="o">&&</span> <span class="nb">cd</span> <span class="nv">$_</span>
</span></span><span class="line"><span class="cl">$ pulumi new aws-hcl
</span></span>

As before, step through the prompts, then open the generated Pulumi project’s main.tf:

<span class="line"><span class="cl"><span class="k">terraform</span> {
</span></span><span class="line"><span class="cl"> <span class="k">required_providers</span> {
</span></span><span class="line"><span class="cl"><span class="n"> aws</span> <span class="o">=</span> {
</span></span><span class="line"><span class="cl"><span class="n"> source</span> <span class="o">=</span> <span class="s2">"pulumi/aws"</span>
</span></span><span class="line"><span class="cl"> }
</span></span><span class="line"><span class="cl"> }
</span></span><span class="line"><span class="cl">}<span class="c1">
</span></span></span><span class="line"><span class="cl"><span class="c1">
</span></span></span><span class="line"><span class="cl"><span class="c1"># Create an AWS resource (S3 Bucket)
</span></span></span><span class="line"><span class="cl"><span class="k">resource</span> <span class="s2">"aws_s3_bucket" "my-bucket"</span> {}<span class="c1">
</span></span></span><span class="line"><span class="cl"><span class="c1">
</span></span></span><span class="line"><span class="cl"><span class="c1"># Export the name of the bucket
</span></span></span><span class="line"><span class="cl"><span class="k">output</span> <span class="s2">"bucket_name"</span> {
</span></span><span class="line"><span class="cl"><span class="n"> value</span> <span class="o">=</span> <span class="k">aws_s3_bucket</span><span class="p">.</span><span class="k">my</span><span class="err">-</span><span class="k">bucket</span><span class="p">.</span><span class="k">id</span>
</span></span><span class="line"><span class="cl">}
</span></span>

You can deploy this out of the box (after setting your AWS credentials again, of course) to see the magic happen:

<span class="line"><span class="cl">$ pulumi up
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Updating <span class="o">(</span>dev<span class="o">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> Type Name Status
</span></span><span class="line"><span class="cl"> + pulumi:pulumi:Stack my-hcl-project-dev created <span class="o">(</span>6s<span class="o">)</span>
</span></span><span class="line"><span class="cl"> + └─ aws:s3:Bucket my-bucket created <span class="o">(</span>3s<span class="o">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Outputs:
</span></span><span class="line"><span class="cl"> bucket_name: <span class="s2">"my-bucket-7282e67"</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Resources:
</span></span><span class="line"><span class="cl"> + <span class="m">2</span> created
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Duration: 7s
</span></span>

This template happens to use the pulumi/aws provider, but you’re free to pull in any others as well: native Pulumi providers, official hashicorp/* providers, community-supported providers, and more. In fact, you can try that now by replacing the code in main.tf with the same code you used in the Terraform program you left off with earlier — only without the explicit terraform > backend block, as it’s no longer needed:

<span class="line"><span class="cl"><span class="k">module</span> <span class="s2">"s3-bucket"</span> {
</span></span><span class="line"><span class="cl"><span class="n"> source</span> <span class="o">=</span> <span class="s2">"tf.pulumi.com/<your-org>/s3-bucket/aws"</span>
</span></span><span class="line"><span class="cl"><span class="n"> version</span> <span class="o">=</span> <span class="s2">"0.1.0"</span>
</span></span><span class="line"><span class="cl"><span class="n"> bucket_prefix</span> <span class="o">=</span> <span class="s2">"my-tf-project-bucket"</span>
</span></span><span class="line"><span class="cl"><span class="n"> tags</span> <span class="o">=</span> {}
</span></span><span class="line"><span class="cl">}
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">output</span> <span class="s2">"bucket_name"</span> {
</span></span><span class="line"><span class="cl"><span class="n"> value</span> <span class="o">=</span> <span class="k">module</span><span class="p">.</span><span class="k">s3</span><span class="err">-</span><span class="k">bucket</span><span class="p">.</span><span class="k">bucket_name</span>
</span></span><span class="line"><span class="cl">}
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">output</span> <span class="s2">"bucket_arn"</span> {
</span></span><span class="line"><span class="cl"><span class="n"> value</span> <span class="o">=</span> <span class="k">module</span><span class="p">.</span><span class="k">s3</span><span class="err">-</span><span class="k">bucket</span><span class="p">.</span><span class="k">bucket_arn</span>
</span></span><span class="line"><span class="cl">}
</span></span>

Run pulumi up again, and you’ll see the module get picked up and used, just as before:

<span class="line"><span class="cl">$ pulumi up
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Updating <span class="o">(</span>dev<span class="o">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> Type Name Status
</span></span><span class="line"><span class="cl"> + pulumi:pulumi:Stack my-hcl-project-dev created <span class="o">(</span>10s<span class="o">)</span>
</span></span><span class="line"><span class="cl"> + └─ components:index:C619d883588c0366 s3-bucket created <span class="o">(</span>8s<span class="o">)</span>
</span></span><span class="line"><span class="cl"> + ├─ random:index:Id s3-bucket.suffix created <span class="o">(</span>0.22s<span class="o">)</span>
</span></span><span class="line"><span class="cl"> + └─ aws:index:S3Bucket s3-bucket.this created <span class="o">(</span>3s<span class="o">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Outputs:
</span></span><span class="line"><span class="cl"> bucket_arn : <span class="s2">"arn:aws:s3:::my-tf-project-bucket-f61cea35"</span>
</span></span><span class="line"><span class="cl"> bucket_name: <span class="s2">"my-tf-project-bucket-f61cea35"</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Resources:
</span></span><span class="line"><span class="cl"> + <span class="m">4</span> created
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Duration: 12s
</span></span>

And with that, our tour is complete. Be sure to clean up both projects with a terraform destroy and pulumi destroy when you’re done.

Where to go next

Now you’ve seen it all come together: You can back your Terraform state with Pulumi Cloud, publish and share your modules, consume those modules from any Pulumi language, and write HCL that runs natively — all without having to rewrite what you’ve already built.

A few next steps to keep the learning going:

We’d love for you to kick the tires on all of this and let us know what you think. If something seems missing or doesn’t behave as you’d expect, open an issue, let us know in the Pulumi Community Slack, or reach out if you’d like to learn more.

Happy building!

Fetched August 4, 2026