Convert HCL Code
When to convert
Converting HCL to Pulumi code makes sense in several scenarios:
- Complex logic: Terraform’s HCL limitations make certain operations difficult
- Testing requirements: You need unit testing capabilities for infrastructure code
- Integration needs: Infrastructure code needs to integrate with application code
- Team preferences: Your team prefers general-purpose programming languages
- Advanced features: You want to use Pulumi-specific features like CrossGuard or Automation API
Conversion approaches
Automated conversion with pulumi convert
The pulumi convert
command can automatically translate Terraform configurations to Pulumi programs.
First, ensure you have a Terraform configuration:
# main.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.aws_region
}
variable "aws_region" {
description = "AWS region"
type = string
default = "us-west-2"
}
variable "instance_type" {
description = "EC2 instance type"
type = string
default = "t3.micro"
}
data "aws_ami" "amazon_linux" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["amzn2-ami-hvm-*-x86_64-gp2"]
}
}
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "main-vpc"
}
}
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
availability_zone = "us-west-2a"
map_public_ip_on_launch = true
tags = {
Name = "public-subnet"
}
}
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = {
Name = "main-igw"
}
}
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
tags = {
Name = "public-rt"
}
}
resource "aws_route_table_association" "public" {
subnet_id = aws_subnet.public.id
route_table_id = aws_route_table.public.id
}
resource "aws_security_group" "web" {
name = "web-sg"
description = "Security group for web servers"
vpc_id = aws_vpc.main.id
ingress {
description = "HTTP"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "SSH"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "web-sg"
}
}
resource "aws_instance" "web" {
ami = data.aws_ami.amazon_linux.id
instance_type = var.instance_type
subnet_id = aws_subnet.public.id
vpc_security_group_ids = [aws_security_group.web.id]
user_data = <<-EOF
#!/bin/bash
yum update -y
yum install -y httpd
systemctl start httpd
systemctl enable httpd
echo "<h1>Hello from Pulumi converted infrastructure!</h1>" > /var/www/html/index.html
EOF
tags = {
Name = "web-server"
}
}
output "vpc_id" {
description = "ID of the VPC"
value = aws_vpc.main.id
}
output "public_ip" {
description = "Public IP address of the web server"
value = aws_instance.web.public_ip
}
output "website_url" {
description = "URL of the website"
value = "http://${aws_instance.web.public_ip}"
}
Now convert it to Pulumi:
# Convert to TypeScript
$ pulumi convert --from terraform --language typescript --out ./pulumi-converted
# Convert to Python
$ pulumi convert --from terraform --language python --out ./pulumi-converted
# Convert to Go
$ pulumi convert --from terraform --language go --out ./pulumi-converted
# Convert to C#
$ pulumi convert --from terraform --language csharp --out ./pulumi-converted
# Convert to Java
$ pulumi convert --from terraform --language java --out ./pulumi-converted
# Convert to YAML
$ pulumi convert --from terraform --language yaml --out ./pulumi-converted
Converted TypeScript example
The pulumi convert
command would generate something like this:
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
// Create configuration
const config = new pulumi.Config();
const awsRegion = config.get("awsRegion") || "us-west-2";
const instanceType = config.get("instanceType") || "t3.micro";
// Data source for Amazon Linux AMI
const amazonLinux = aws.ec2.getAmiOutput({
mostRecent: true,
owners: ["amazon"],
filters: [{
name: "name",
values: ["amzn2-ami-hvm-*-x86_64-gp2"],
}],
});
// Create VPC
const main = new aws.ec2.Vpc("main", {
cidrBlock: "10.0.0.0/16",
enableDnsHostnames: true,
enableDnsSupport: true,
tags: {
Name: "main-vpc",
},
});
// Create public subnet
const publicSubnet = new aws.ec2.Subnet("public", {
vpcId: main.id,
cidrBlock: "10.0.1.0/24",
availabilityZone: "us-west-2a",
mapPublicIpOnLaunch: true,
tags: {
Name: "public-subnet",
},
});
// Create internet gateway
const mainIgw = new aws.ec2.InternetGateway("main", {
vpcId: main.id,
tags: {
Name: "main-igw",
},
});
// Create route table
const publicRt = new aws.ec2.RouteTable("public", {
vpcId: main.id,
routes: [{
cidrBlock: "0.0.0.0/0",
gatewayId: mainIgw.id,
}],
tags: {
Name: "public-rt",
},
});
// Associate route table with subnet
const publicRtAssociation = new aws.ec2.RouteTableAssociation("public", {
subnetId: publicSubnet.id,
routeTableId: publicRt.id,
});
// Create security group
const webSg = new aws.ec2.SecurityGroup("web", {
name: "web-sg",
description: "Security group for web servers",
vpcId: main.id,
ingress: [
{
description: "HTTP",
fromPort: 80,
toPort: 80,
protocol: "tcp",
cidrBlocks: ["0.0.0.0/0"],
},
{
description: "SSH",
fromPort: 22,
toPort: 22,
protocol: "tcp",
cidrBlocks: ["0.0.0.0/0"],
},
],
egress: [{
fromPort: 0,
toPort: 0,
protocol: "-1",
cidrBlocks: ["0.0.0.0/0"],
}],
tags: {
Name: "web-sg",
},
});
// Create EC2 instance
const webInstance = new aws.ec2.Instance("web", {
ami: amazonLinux.id,
instanceType: instanceType,
subnetId: publicSubnet.id,
vpcSecurityGroupIds: [webSg.id],
userData: `#!/bin/bash
yum update -y
yum install -y httpd
systemctl start httpd
systemctl enable httpd
echo "<h1>Hello from Pulumi converted infrastructure!</h1>" > /var/www/html/index.html
`,
tags: {
Name: "web-server",
},
});
// Outputs
export const vpcId = main.id;
export const publicIp = webInstance.publicIp;
export const websiteUrl = pulumi.interpolate`http://${webInstance.publicIp}`;
import pulumi
import pulumi_aws as aws
# Create configuration
config = pulumi.Config()
aws_region = config.get("aws_region") or "us-west-2"
instance_type = config.get("instance_type") or "t3.micro"
# Data source for Amazon Linux AMI
amazon_linux = aws.ec2.get_ami(
most_recent=True,
owners=["amazon"],
filters=[{
"name": "name",
"values": ["amzn2-ami-hvm-*-x86_64-gp2"],
}]
)
# Create VPC
main_vpc = aws.ec2.Vpc("main",
cidr_block="10.0.0.0/16",
enable_dns_hostnames=True,
enable_dns_support=True,
tags={
"Name": "main-vpc",
}
)
# Create public subnet
public_subnet = aws.ec2.Subnet("public",
vpc_id=main_vpc.id,
cidr_block="10.0.1.0/24",
availability_zone="us-west-2a",
map_public_ip_on_launch=True,
tags={
"Name": "public-subnet",
}
)
# Create internet gateway
main_igw = aws.ec2.InternetGateway("main",
vpc_id=main_vpc.id,
tags={
"Name": "main-igw",
}
)
# Create route table
public_rt = aws.ec2.RouteTable("public",
vpc_id=main_vpc.id,
routes=[{
"cidr_block": "0.0.0.0/0",
"gateway_id": main_igw.id,
}],
tags={
"Name": "public-rt",
}
)
# Associate route table with subnet
public_rt_association = aws.ec2.RouteTableAssociation("public",
subnet_id=public_subnet.id,
route_table_id=public_rt.id
)
# Create security group
web_sg = aws.ec2.SecurityGroup("web",
name="web-sg",
description="Security group for web servers",
vpc_id=main_vpc.id,
ingress=[
{
"description": "HTTP",
"from_port": 80,
"to_port": 80,
"protocol": "tcp",
"cidr_blocks": ["0.0.0.0/0"],
},
{
"description": "SSH",
"from_port": 22,
"to_port": 22,
"protocol": "tcp",
"cidr_blocks": ["0.0.0.0/0"],
},
],
egress=[{
"from_port": 0,
"to_port": 0,
"protocol": "-1",
"cidr_blocks": ["0.0.0.0/0"],
}],
tags={
"Name": "web-sg",
}
)
# Create EC2 instance
web_instance = aws.ec2.Instance("web",
ami=amazon_linux.id,
instance_type=instance_type,
subnet_id=public_subnet.id,
vpc_security_group_ids=[web_sg.id],
user_data="""#!/bin/bash
yum update -y
yum install -y httpd
systemctl start httpd
systemctl enable httpd
echo "<h1>Hello from Pulumi converted infrastructure!</h1>" > /var/www/html/index.html
""",
tags={
"Name": "web-server",
}
)
# Outputs
pulumi.export("vpc_id", main_vpc.id)
pulumi.export("public_ip", web_instance.public_ip)
pulumi.export("website_url", pulumi.Output.format("http://{0}", web_instance.public_ip))
package main
import (
"github.com/pulumi/pulumi-aws/sdk/v6/go/aws/ec2"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi/config"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
// Create configuration
cfg := config.New(ctx, "")
awsRegion := cfg.Get("awsRegion")
if awsRegion == "" {
awsRegion = "us-west-2"
}
instanceType := cfg.Get("instanceType")
if instanceType == "" {
instanceType = "t3.micro"
}
// Data source for Amazon Linux AMI
amazonLinux, err := ec2.LookupAmi(ctx, &ec2.LookupAmiArgs{
MostRecent: pulumi.BoolRef(true),
Owners: []string{"amazon"},
Filters: []ec2.GetAmiFilter{
{
Name: "name",
Values: []string{"amzn2-ami-hvm-*-x86_64-gp2"},
},
},
})
if err != nil {
return err
}
// Create VPC
mainVpc, err := ec2.NewVpc(ctx, "main", &ec2.VpcArgs{
CidrBlock: pulumi.String("10.0.0.0/16"),
EnableDnsHostnames: pulumi.Bool(true),
EnableDnsSupport: pulumi.Bool(true),
Tags: pulumi.StringMap{
"Name": pulumi.String("main-vpc"),
},
})
if err != nil {
return err
}
// Create public subnet
publicSubnet, err := ec2.NewSubnet(ctx, "public", &ec2.SubnetArgs{
VpcId: mainVpc.ID(),
CidrBlock: pulumi.String("10.0.1.0/24"),
AvailabilityZone: pulumi.String("us-west-2a"),
MapPublicIpOnLaunch: pulumi.Bool(true),
Tags: pulumi.StringMap{
"Name": pulumi.String("public-subnet"),
},
})
if err != nil {
return err
}
// Create internet gateway
mainIgw, err := ec2.NewInternetGateway(ctx, "main", &ec2.InternetGatewayArgs{
VpcId: mainVpc.ID(),
Tags: pulumi.StringMap{
"Name": pulumi.String("main-igw"),
},
})
if err != nil {
return err
}
// Create route table
publicRt, err := ec2.NewRouteTable(ctx, "public", &ec2.RouteTableArgs{
VpcId: mainVpc.ID(),
Routes: ec2.RouteTableRouteArray{
&ec2.RouteTableRouteArgs{
CidrBlock: pulumi.String("0.0.0.0/0"),
GatewayId: mainIgw.ID(),
},
},
Tags: pulumi.StringMap{
"Name": pulumi.String("public-rt"),
},
})
if err != nil {
return err
}
// Associate route table with subnet
_, err = ec2.NewRouteTableAssociation(ctx, "public", &ec2.RouteTableAssociationArgs{
SubnetId: publicSubnet.ID(),
RouteTableId: publicRt.ID(),
})
if err != nil {
return err
}
// Create security group
webSg, err := ec2.NewSecurityGroup(ctx, "web", &ec2.SecurityGroupArgs{
Name: pulumi.String("web-sg"),
Description: pulumi.String("Security group for web servers"),
VpcId: mainVpc.ID(),
Ingress: ec2.SecurityGroupIngressArray{
&ec2.SecurityGroupIngressArgs{
Description: pulumi.String("HTTP"),
FromPort: pulumi.Int(80),
ToPort: pulumi.Int(80),
Protocol: pulumi.String("tcp"),
CidrBlocks: pulumi.StringArray{pulumi.String("0.0.0.0/0")},
},
&ec2.SecurityGroupIngressArgs{
Description: pulumi.String("SSH"),
FromPort: pulumi.Int(22),
ToPort: pulumi.Int(22),
Protocol: pulumi.String("tcp"),
CidrBlocks: pulumi.StringArray{pulumi.String("0.0.0.0/0")},
},
},
Egress: ec2.SecurityGroupEgressArray{
&ec2.SecurityGroupEgressArgs{
FromPort: pulumi.Int(0),
ToPort: pulumi.Int(0),
Protocol: pulumi.String("-1"),
CidrBlocks: pulumi.StringArray{pulumi.String("0.0.0.0/0")},
},
},
Tags: pulumi.StringMap{
"Name": pulumi.String("web-sg"),
},
})
if err != nil {
return err
}
// Create EC2 instance
webInstance, err := ec2.NewInstance(ctx, "web", &ec2.InstanceArgs{
Ami: pulumi.String(amazonLinux.Id),
InstanceType: pulumi.String(instanceType),
SubnetId: publicSubnet.ID(),
VpcSecurityGroupIds: pulumi.StringArray{webSg.ID()},
UserData: pulumi.String(`#!/bin/bash
yum update -y
yum install -y httpd
systemctl start httpd
systemctl enable httpd
echo "<h1>Hello from Pulumi converted infrastructure!</h1>" > /var/www/html/index.html
`),
Tags: pulumi.StringMap{
"Name": pulumi.String("web-server"),
},
})
if err != nil {
return err
}
// Outputs
ctx.Export("vpcId", mainVpc.ID())
ctx.Export("publicIp", webInstance.PublicIp)
ctx.Export("websiteUrl", pulumi.Sprintf("http://%s", webInstance.PublicIp))
return nil
})
}
using System.Collections.Generic;
using Pulumi;
using Pulumi.Aws.Ec2;
return await Deployment.RunAsync(() =>
{
// Create configuration
var config = new Pulumi.Config();
var awsRegion = config.Get("awsRegion") ?? "us-west-2";
var instanceType = config.Get("instanceType") ?? "t3.micro";
// Data source for Amazon Linux AMI
var amazonLinux = GetAmi.Invoke(new GetAmiInvokeArgs
{
MostRecent = true,
Owners = new[] { "amazon" },
Filters = new[]
{
new GetAmiFilterInputArgs
{
Name = "name",
Values = new[] { "amzn2-ami-hvm-*-x86_64-gp2" },
},
},
});
// Create VPC
var mainVpc = new Vpc("main", new VpcArgs
{
CidrBlock = "10.0.0.0/16",
EnableDnsHostnames = true,
EnableDnsSupport = true,
Tags = new Dictionary<string, string>
{
["Name"] = "main-vpc",
},
});
// Create public subnet
var publicSubnet = new Subnet("public", new SubnetArgs
{
VpcId = mainVpc.Id,
CidrBlock = "10.0.1.0/24",
AvailabilityZone = "us-west-2a",
MapPublicIpOnLaunch = true,
Tags = new Dictionary<string, string>
{
["Name"] = "public-subnet",
},
});
// Create internet gateway
var mainIgw = new InternetGateway("main", new InternetGatewayArgs
{
VpcId = mainVpc.Id,
Tags = new Dictionary<string, string>
{
["Name"] = "main-igw",
},
});
// Create route table
var publicRt = new RouteTable("public", new RouteTableArgs
{
VpcId = mainVpc.Id,
Routes = new[]
{
new RouteTableRouteArgs
{
CidrBlock = "0.0.0.0/0",
GatewayId = mainIgw.Id,
},
},
Tags = new Dictionary<string, string>
{
["Name"] = "public-rt",
},
});
// Associate route table with subnet
var publicRtAssociation = new RouteTableAssociation("public", new RouteTableAssociationArgs
{
SubnetId = publicSubnet.Id,
RouteTableId = publicRt.Id,
});
// Create security group
var webSg = new SecurityGroup("web", new SecurityGroupArgs
{
Name = "web-sg",
Description = "Security group for web servers",
VpcId = mainVpc.Id,
Ingress = new[]
{
new SecurityGroupIngressArgs
{
Description = "HTTP",
FromPort = 80,
ToPort = 80,
Protocol = "tcp",
CidrBlocks = new[] { "0.0.0.0/0" },
},
new SecurityGroupIngressArgs
{
Description = "SSH",
FromPort = 22,
ToPort = 22,
Protocol = "tcp",
CidrBlocks = new[] { "0.0.0.0/0" },
},
},
Egress = new[]
{
new SecurityGroupEgressArgs
{
FromPort = 0,
ToPort = 0,
Protocol = "-1",
CidrBlocks = new[] { "0.0.0.0/0" },
},
},
Tags = new Dictionary<string, string>
{
["Name"] = "web-sg",
},
});
// Create EC2 instance
var webInstance = new Instance("web", new InstanceArgs
{
Ami = amazonLinux.Apply(ami => ami.Id),
InstanceType = instanceType,
SubnetId = publicSubnet.Id,
VpcSecurityGroupIds = new[] { webSg.Id },
UserData = @"#!/bin/bash
yum update -y
yum install -y httpd
systemctl start httpd
systemctl enable httpd
echo ""<h1>Hello from Pulumi converted infrastructure!</h1>"" > /var/www/html/index.html
",
Tags = new Dictionary<string, string>
{
["Name"] = "web-server",
},
});
return new Dictionary<string, object?>
{
["vpcId"] = mainVpc.Id,
["publicIp"] = webInstance.PublicIp,
["websiteUrl"] = webInstance.PublicIp.Apply(ip => $"http://{ip}"),
};
});
package myproject;
import com.pulumi.Pulumi;
import com.pulumi.aws.ec2.Ec2Functions;
import com.pulumi.aws.ec2.Instance;
import com.pulumi.aws.ec2.InstanceArgs;
import com.pulumi.aws.ec2.InternetGateway;
import com.pulumi.aws.ec2.InternetGatewayArgs;
import com.pulumi.aws.ec2.RouteTable;
import com.pulumi.aws.ec2.RouteTableArgs;
import com.pulumi.aws.ec2.RouteTableAssociation;
import com.pulumi.aws.ec2.RouteTableAssociationArgs;
import com.pulumi.aws.ec2.SecurityGroup;
import com.pulumi.aws.ec2.SecurityGroupArgs;
import com.pulumi.aws.ec2.Subnet;
import com.pulumi.aws.ec2.SubnetArgs;
import com.pulumi.aws.ec2.Vpc;
import com.pulumi.aws.ec2.VpcArgs;
import com.pulumi.aws.ec2.inputs.GetAmiArgs;
import com.pulumi.aws.ec2.inputs.GetAmiFilterArgs;
import com.pulumi.aws.ec2.inputs.RouteTableRouteArgs;
import com.pulumi.aws.ec2.inputs.SecurityGroupEgressArgs;
import com.pulumi.aws.ec2.inputs.SecurityGroupIngressArgs;
import com.pulumi.core.Output;
import java.util.List;
import java.util.Map;
public class App {
public static void main(String[] args) {
Pulumi.run(ctx -> {
// Create configuration
var config = new com.pulumi.Config();
var awsRegion = config.get("awsRegion").orElse("us-west-2");
var instanceType = config.get("instanceType").orElse("t3.micro");
// Data source for Amazon Linux AMI
var amazonLinux = Ec2Functions.getAmi(GetAmiArgs.builder()
.mostRecent(true)
.owners("amazon")
.filters(GetAmiFilterArgs.builder()
.name("name")
.values("amzn2-ami-hvm-*-x86_64-gp2")
.build())
.build());
// Create VPC
var mainVpc = new Vpc("main", VpcArgs.builder()
.cidrBlock("10.0.0.0/16")
.enableDnsHostnames(true)
.enableDnsSupport(true)
.tags(Map.of("Name", "main-vpc"))
.build());
// Create public subnet
var publicSubnet = new Subnet("public", SubnetArgs.builder()
.vpcId(mainVpc.id())
.cidrBlock("10.0.1.0/24")
.availabilityZone("us-west-2a")
.mapPublicIpOnLaunch(true)
.tags(Map.of("Name", "public-subnet"))
.build());
// Create internet gateway
var mainIgw = new InternetGateway("main", InternetGatewayArgs.builder()
.vpcId(mainVpc.id())
.tags(Map.of("Name", "main-igw"))
.build());
// Create route table
var publicRt = new RouteTable("public", RouteTableArgs.builder()
.vpcId(mainVpc.id())
.routes(RouteTableRouteArgs.builder()
.cidrBlock("0.0.0.0/0")
.gatewayId(mainIgw.id())
.build())
.tags(Map.of("Name", "public-rt"))
.build());
// Associate route table with subnet
var publicRtAssociation = new RouteTableAssociation("public", RouteTableAssociationArgs.builder()
.subnetId(publicSubnet.id())
.routeTableId(publicRt.id())
.build());
// Create security group
var webSg = new SecurityGroup("web", SecurityGroupArgs.builder()
.name("web-sg")
.description("Security group for web servers")
.vpcId(mainVpc.id())
.ingress(
SecurityGroupIngressArgs.builder()
.description("HTTP")
.fromPort(80)
.toPort(80)
.protocol("tcp")
.cidrBlocks("0.0.0.0/0")
.build(),
SecurityGroupIngressArgs.builder()
.description("SSH")
.fromPort(22)
.toPort(22)
.protocol("tcp")
.cidrBlocks("0.0.0.0/0")
.build()
)
.egress(SecurityGroupEgressArgs.builder()
.fromPort(0)
.toPort(0)
.protocol("-1")
.cidrBlocks("0.0.0.0/0")
.build())
.tags(Map.of("Name", "web-sg"))
.build());
// Create EC2 instance
var webInstance = new Instance("web", InstanceArgs.builder()
.ami(amazonLinux.applyValue(ami -> ami.id()))
.instanceType(instanceType)
.subnetId(publicSubnet.id())
.vpcSecurityGroupIds(webSg.id())
.userData("""
#!/bin/bash
yum update -y
yum install -y httpd
systemctl start httpd
systemctl enable httpd
echo "<h1>Hello from Pulumi converted infrastructure!</h1>" > /var/www/html/index.html
""")
.tags(Map.of("Name", "web-server"))
.build());
// Outputs
ctx.export("vpcId", mainVpc.id());
ctx.export("publicIp", webInstance.publicIp());
ctx.export("websiteUrl", webInstance.publicIp().applyValue(ip -> String.format("http://%s", ip)));
});
}
}
name: converted-infrastructure
runtime: yaml
description: Converted from Terraform HCL
config:
awsRegion:
type: string
default: us-west-2
instanceType:
type: string
default: t3.micro
variables:
# Data source for Amazon Linux AMI
amazonLinux:
fn::invoke:
function: aws:ec2:getAmi
arguments:
mostRecent: true
owners: ["amazon"]
filters:
- name: name
values: ["amzn2-ami-hvm-*-x86_64-gp2"]
resources:
# Create VPC
main:
type: aws:ec2:Vpc
properties:
cidrBlock: 10.0.0.0/16
enableDnsHostnames: true
enableDnsSupport: true
tags:
Name: main-vpc
# Create public subnet
public:
type: aws:ec2:Subnet
properties:
vpcId: ${main.id}
cidrBlock: 10.0.1.0/24
availabilityZone: us-west-2a
mapPublicIpOnLaunch: true
tags:
Name: public-subnet
# Create internet gateway
main-igw:
type: aws:ec2:InternetGateway
properties:
vpcId: ${main.id}
tags:
Name: main-igw
# Create route table
public-rt:
type: aws:ec2:RouteTable
properties:
vpcId: ${main.id}
routes:
- cidrBlock: 0.0.0.0/0
gatewayId: ${main-igw.id}
tags:
Name: public-rt
# Associate route table with subnet
public-rt-association:
type: aws:ec2:RouteTableAssociation
properties:
subnetId: ${public.id}
routeTableId: ${public-rt.id}
# Create security group
web-sg:
type: aws:ec2:SecurityGroup
properties:
name: web-sg
description: Security group for web servers
vpcId: ${main.id}
ingress:
- description: HTTP
fromPort: 80
toPort: 80
protocol: tcp
cidrBlocks: ["0.0.0.0/0"]
- description: SSH
fromPort: 22
toPort: 22
protocol: tcp
cidrBlocks: ["0.0.0.0/0"]
egress:
- fromPort: 0
toPort: 0
protocol: "-1"
cidrBlocks: ["0.0.0.0/0"]
tags:
Name: web-sg
# Create EC2 instance
web:
type: aws:ec2:Instance
properties:
ami: ${amazonLinux.id}
instanceType: ${instanceType}
subnetId: ${public.id}
vpcSecurityGroupIds: [${web-sg.id}]
userData: |
#!/bin/bash
yum update -y
yum install -y httpd
systemctl start httpd
systemctl enable httpd
echo "<h1>Hello from Pulumi converted infrastructure!</h1>" > /var/www/html/index.html
tags:
Name: web-server
outputs:
vpcId: ${main.id}
publicIp: ${web.publicIp}
websiteUrl: http://${web.publicIp}
Testing the converted code
Deploy and verify that the converted code produces the same infrastructure:
# Initialize the new Pulumi project
$ cd pulumi-converted
$ pulumi up
# Test the deployment
$ curl $(pulumi stack output websiteUrl)
# Compare with original Terraform outputs
$ terraform output -json > tf-outputs.json
$ pulumi stack output --json > pulumi-outputs.json
# Clean up
$ pulumi destroy
Verifying conversion accuracy
After converting existing infrastructure, verify that your Pulumi program produces identical results by importing the Terraform state and running a preview:
# Import the existing Terraform state
$ pulumi import aws:ec2/vpc:Vpc main vpc-12345
$ pulumi import aws:ec2/subnet:Subnet public subnet-67890
$ pulumi import aws:ec2/instance:Instance web i-abcdef123
# Run preview to ensure no changes
$ pulumi preview
# Expected result: "no changes required"
This verification step is crucial when converting production infrastructure, as it confirms your Pulumi program exactly matches the existing Terraform-managed resources.
AI-assisted conversion with the Pulumi MCP server
For complex Terraform configurations, you can use AI tools like Claude with the Pulumi MCP (Model Context Protocol) server, which provides comprehensive Pulumi integration, including a specialized Terraform conversion prompt.
Using the Pulumi MCP server (recommended)
The Pulumi MCP server is an open-source tool that enables AI assistants to interact with Pulumi programmatically. Beyond conversion, it provides full infrastructure management capabilities including stack operations, resource querying, and automated deployments.
The MCP server includes a sophisticated convert-terraform-to-typescript
prompt that ensures:
- Type safety: Proper use of
pulumi.Input<T>
andpulumi.Output<T>
types - Best practices: Idiomatic TypeScript patterns and Pulumi conventions
- Configuration handling: Safe access to config values with null checking
- Resource naming: Consistent and descriptive resource naming
- Multi-provider support: Proper handling of multiple provider configurations
Installation and setup:
Install via Claude Code (if using Claude):
$ claude mcp add -s user pulumi -- npx @pulumi/mcp-server@latest stdio
Follow the complete setup instructions in the MCP server README.
Prepare your Terraform code: Gather your complete Terraform configuration files (
.tf
,terraform.tfvars
, etc.)Use the conversion prompt: Once configured, you can attach the specific conversion prompt in Claude:
@convert-terraform-to-typescript Please convert this Terraform configuration to Pulumi TypeScript: [Paste your Terraform HCL code here]
The MCP server provides additional capabilities beyond conversion, including:
- Infrastructure previews with
pulumi preview
- Automated deployments with
pulumi up
- Stack output retrieval
- Resource querying and management
Alternative: Manual prompt usage
If you prefer not to use the MCP server, you can access the conversion prompt directly:
Access the prompt: The “convert-terraform-to-typescript” prompt is available in the Pulumi MCP server repository
Prepare your Terraform code: Gather your complete Terraform configuration files (
.tf
,terraform.tfvars
, etc.)Use with Claude: Copy the conversion prompt and your Terraform code, then ask Claude to perform the conversion:
[Paste the complete conversion prompt] Please convert this Terraform configuration to Pulumi TypeScript: [Paste your Terraform HCL code here]
Review the output
Any time you use an automated conversion tool, you will want to review and validate the output. Some things to check for:
- Proper error handling and validation
- Type-safe configuration access
- Idiomatic resource definitions
- Comprehensive resource labeling
Best practices for conversion
- Start small: Convert simple configurations first to understand the process
- Verify outputs: Ensure converted code produces identical infrastructure
- Test thoroughly: Write tests for critical infrastructure components
- Preserve structure: Keep similar resource organization when possible
- Document changes: Note any differences between original and converted code
- Version control: Use Git to track conversion changes
Thank you for your feedback!
If you have a question about how to use Pulumi, reach out in Community Slack.
Open an issue on GitHub to report a problem or suggest an improvement.