The aws:ec2/instance:Instance resource, part of the Pulumi AWS provider, provisions EC2 virtual machine instances: their AMI, instance type, networking, and compute configuration. This guide focuses on four capabilities: AMI selection strategies, spot instance requests, network interface attachment, and CPU topology configuration.
An EC2 instance doesn’t exist in isolation. It requires an AMI and runs in a VPC with subnets and security groups. Examples may reference IAM roles, SSH key pairs, or other infrastructure that must exist separately. The examples are intentionally small and show how each capability is configured. Combine them with your own VPC, security groups, and access configuration.
Launch instances with dynamically filtered AMIs
Most deployments start by selecting an AMI that matches OS and architecture requirements. Dynamic AMI lookup with filters ensures you always use the latest version without hardcoding IDs.
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const ubuntu = aws.ec2.getAmi({
mostRecent: true,
filters: [
{
name: "name",
values: ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"],
},
{
name: "virtualization-type",
values: ["hvm"],
},
],
owners: ["099720109477"],
});
const example = new aws.ec2.Instance("example", {
ami: ubuntu.then(ubuntu => ubuntu.id),
instanceType: aws.ec2.InstanceType.T3_Micro,
tags: {
Name: "HelloWorld",
},
});
import pulumi
import pulumi_aws as aws
ubuntu = aws.ec2.get_ami(most_recent=True,
filters=[
{
"name": "name",
"values": ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"],
},
{
"name": "virtualization-type",
"values": ["hvm"],
},
],
owners=["099720109477"])
example = aws.ec2.Instance("example",
ami=ubuntu.id,
instance_type=aws.ec2.InstanceType.T3_MICRO,
tags={
"Name": "HelloWorld",
})
package main
import (
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ec2"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
ubuntu, err := ec2.LookupAmi(ctx, &ec2.LookupAmiArgs{
MostRecent: pulumi.BoolRef(true),
Filters: []ec2.GetAmiFilter{
{
Name: "name",
Values: []string{
"ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*",
},
},
{
Name: "virtualization-type",
Values: []string{
"hvm",
},
},
},
Owners: []string{
"099720109477",
},
}, nil)
if err != nil {
return err
}
_, err = ec2.NewInstance(ctx, "example", &ec2.InstanceArgs{
Ami: pulumi.String(ubuntu.Id),
InstanceType: pulumi.String(ec2.InstanceType_T3_Micro),
Tags: pulumi.StringMap{
"Name": pulumi.String("HelloWorld"),
},
})
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;
return await Deployment.RunAsync(() =>
{
var ubuntu = Aws.Ec2.GetAmi.Invoke(new()
{
MostRecent = true,
Filters = new[]
{
new Aws.Ec2.Inputs.GetAmiFilterInputArgs
{
Name = "name",
Values = new[]
{
"ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*",
},
},
new Aws.Ec2.Inputs.GetAmiFilterInputArgs
{
Name = "virtualization-type",
Values = new[]
{
"hvm",
},
},
},
Owners = new[]
{
"099720109477",
},
});
var example = new Aws.Ec2.Instance("example", new()
{
Ami = ubuntu.Apply(getAmiResult => getAmiResult.Id),
InstanceType = Aws.Ec2.InstanceType.T3_Micro,
Tags =
{
{ "Name", "HelloWorld" },
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.ec2.Ec2Functions;
import com.pulumi.aws.ec2.inputs.GetAmiArgs;
import com.pulumi.aws.ec2.Instance;
import com.pulumi.aws.ec2.InstanceArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) {
Pulumi.run(App::stack);
}
public static void stack(Context ctx) {
final var ubuntu = Ec2Functions.getAmi(GetAmiArgs.builder()
.mostRecent(true)
.filters(
GetAmiFilterArgs.builder()
.name("name")
.values("ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*")
.build(),
GetAmiFilterArgs.builder()
.name("virtualization-type")
.values("hvm")
.build())
.owners("099720109477")
.build());
var example = new Instance("example", InstanceArgs.builder()
.ami(ubuntu.id())
.instanceType("t3.micro")
.tags(Map.of("Name", "HelloWorld"))
.build());
}
}
resources:
example:
type: aws:ec2:Instance
properties:
ami: ${ubuntu.id}
instanceType: t3.micro
tags:
Name: HelloWorld
variables:
ubuntu:
fn::invoke:
function: aws:ec2:getAmi
arguments:
mostRecent: true
filters:
- name: name
values:
- ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*
- name: virtualization-type
values:
- hvm
owners:
- '099720109477'
The getAmi data source queries AWS for AMIs matching your filters. The filters array narrows results by name patterns and virtualization type, while owners restricts results to trusted publishers. The ami property references the lookup result, ensuring the instance always launches with the latest matching AMI.
Reference AMIs from Systems Manager Parameter Store
AWS publishes AMI IDs to Systems Manager Parameter Store, providing stable paths to official AMI catalogs without requiring dynamic lookups.
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const example = new aws.ec2.Instance("example", {
ami: "resolve:ssm:/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64",
instanceType: aws.ec2.InstanceType.T3_Micro,
tags: {
Name: "HelloWorld",
},
});
import pulumi
import pulumi_aws as aws
example = aws.ec2.Instance("example",
ami="resolve:ssm:/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64",
instance_type=aws.ec2.InstanceType.T3_MICRO,
tags={
"Name": "HelloWorld",
})
package main
import (
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ec2"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
_, err := ec2.NewInstance(ctx, "example", &ec2.InstanceArgs{
Ami: pulumi.String("resolve:ssm:/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64"),
InstanceType: pulumi.String(ec2.InstanceType_T3_Micro),
Tags: pulumi.StringMap{
"Name": pulumi.String("HelloWorld"),
},
})
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;
return await Deployment.RunAsync(() =>
{
var example = new Aws.Ec2.Instance("example", new()
{
Ami = "resolve:ssm:/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64",
InstanceType = Aws.Ec2.InstanceType.T3_Micro,
Tags =
{
{ "Name", "HelloWorld" },
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.ec2.Instance;
import com.pulumi.aws.ec2.InstanceArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) {
Pulumi.run(App::stack);
}
public static void stack(Context ctx) {
var example = new Instance("example", InstanceArgs.builder()
.ami("resolve:ssm:/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64")
.instanceType("t3.micro")
.tags(Map.of("Name", "HelloWorld"))
.build());
}
}
resources:
example:
type: aws:ec2:Instance
properties:
ami: resolve:ssm:/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64
instanceType: t3.micro
tags:
Name: HelloWorld
The resolve:ssm prefix tells EC2 to resolve the parameter value at launch time. This approach provides stable parameter paths for AWS-managed AMIs without requiring a separate data source lookup. Your IAM credentials need permissions to read SSM parameters.
Request spot instances with price limits
Workloads that tolerate interruption can use spot instances to reduce compute costs by bidding on spare EC2 capacity.
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const example = aws.ec2.getAmi({
mostRecent: true,
owners: ["amazon"],
filters: [
{
name: "architecture",
values: ["arm64"],
},
{
name: "name",
values: ["al2023-ami-2023*"],
},
],
});
const exampleInstance = new aws.ec2.Instance("example", {
ami: example.then(example => example.id),
instanceMarketOptions: {
marketType: "spot",
spotOptions: {
maxPrice: "0.0031",
},
},
instanceType: aws.ec2.InstanceType.T4g_Nano,
tags: {
Name: "test-spot",
},
});
import pulumi
import pulumi_aws as aws
example = aws.ec2.get_ami(most_recent=True,
owners=["amazon"],
filters=[
{
"name": "architecture",
"values": ["arm64"],
},
{
"name": "name",
"values": ["al2023-ami-2023*"],
},
])
example_instance = aws.ec2.Instance("example",
ami=example.id,
instance_market_options={
"market_type": "spot",
"spot_options": {
"max_price": "0.0031",
},
},
instance_type=aws.ec2.InstanceType.T4G_NANO,
tags={
"Name": "test-spot",
})
package main
import (
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ec2"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
example, err := ec2.LookupAmi(ctx, &ec2.LookupAmiArgs{
MostRecent: pulumi.BoolRef(true),
Owners: []string{
"amazon",
},
Filters: []ec2.GetAmiFilter{
{
Name: "architecture",
Values: []string{
"arm64",
},
},
{
Name: "name",
Values: []string{
"al2023-ami-2023*",
},
},
},
}, nil)
if err != nil {
return err
}
_, err = ec2.NewInstance(ctx, "example", &ec2.InstanceArgs{
Ami: pulumi.String(example.Id),
InstanceMarketOptions: &ec2.InstanceInstanceMarketOptionsArgs{
MarketType: pulumi.String("spot"),
SpotOptions: &ec2.InstanceInstanceMarketOptionsSpotOptionsArgs{
MaxPrice: pulumi.String("0.0031"),
},
},
InstanceType: pulumi.String(ec2.InstanceType_T4g_Nano),
Tags: pulumi.StringMap{
"Name": pulumi.String("test-spot"),
},
})
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;
return await Deployment.RunAsync(() =>
{
var example = Aws.Ec2.GetAmi.Invoke(new()
{
MostRecent = true,
Owners = new[]
{
"amazon",
},
Filters = new[]
{
new Aws.Ec2.Inputs.GetAmiFilterInputArgs
{
Name = "architecture",
Values = new[]
{
"arm64",
},
},
new Aws.Ec2.Inputs.GetAmiFilterInputArgs
{
Name = "name",
Values = new[]
{
"al2023-ami-2023*",
},
},
},
});
var exampleInstance = new Aws.Ec2.Instance("example", new()
{
Ami = example.Apply(getAmiResult => getAmiResult.Id),
InstanceMarketOptions = new Aws.Ec2.Inputs.InstanceInstanceMarketOptionsArgs
{
MarketType = "spot",
SpotOptions = new Aws.Ec2.Inputs.InstanceInstanceMarketOptionsSpotOptionsArgs
{
MaxPrice = "0.0031",
},
},
InstanceType = Aws.Ec2.InstanceType.T4g_Nano,
Tags =
{
{ "Name", "test-spot" },
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.ec2.Ec2Functions;
import com.pulumi.aws.ec2.inputs.GetAmiArgs;
import com.pulumi.aws.ec2.Instance;
import com.pulumi.aws.ec2.InstanceArgs;
import com.pulumi.aws.ec2.inputs.InstanceInstanceMarketOptionsArgs;
import com.pulumi.aws.ec2.inputs.InstanceInstanceMarketOptionsSpotOptionsArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) {
Pulumi.run(App::stack);
}
public static void stack(Context ctx) {
final var example = Ec2Functions.getAmi(GetAmiArgs.builder()
.mostRecent(true)
.owners("amazon")
.filters(
GetAmiFilterArgs.builder()
.name("architecture")
.values("arm64")
.build(),
GetAmiFilterArgs.builder()
.name("name")
.values("al2023-ami-2023*")
.build())
.build());
var exampleInstance = new Instance("exampleInstance", InstanceArgs.builder()
.ami(example.id())
.instanceMarketOptions(InstanceInstanceMarketOptionsArgs.builder()
.marketType("spot")
.spotOptions(InstanceInstanceMarketOptionsSpotOptionsArgs.builder()
.maxPrice("0.0031")
.build())
.build())
.instanceType("t4g.nano")
.tags(Map.of("Name", "test-spot"))
.build());
}
}
resources:
exampleInstance:
type: aws:ec2:Instance
name: example
properties:
ami: ${example.id}
instanceMarketOptions:
marketType: spot
spotOptions:
maxPrice: 0.0031
instanceType: t4g.nano
tags:
Name: test-spot
variables:
example:
fn::invoke:
function: aws:ec2:getAmi
arguments:
mostRecent: true
owners:
- amazon
filters:
- name: architecture
values:
- arm64
- name: name
values:
- al2023-ami-2023*
The instanceMarketOptions property enables spot purchasing. Inside spotOptions, maxPrice sets your maximum bid per hour. When spot prices exceed your limit or capacity becomes unavailable, AWS terminates the instance. The example defaults to terminate on interruption since spotInstanceInterruptionBehavior isn’t specified.
Attach pre-created network interfaces at launch
Applications that require specific IP addresses or advanced network configuration can attach pre-created network interfaces rather than letting EC2 create them automatically.
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const myVpc = new aws.ec2.Vpc("my_vpc", {
cidrBlock: "172.16.0.0/16",
tags: {
Name: "tf-example",
},
});
const mySubnet = new aws.ec2.Subnet("my_subnet", {
vpcId: myVpc.id,
cidrBlock: "172.16.10.0/24",
availabilityZone: "us-west-2a",
tags: {
Name: "tf-example",
},
});
const example = new aws.ec2.NetworkInterface("example", {
subnetId: mySubnet.id,
privateIps: ["172.16.10.100"],
tags: {
Name: "primary_network_interface",
},
});
const exampleInstance = new aws.ec2.Instance("example", {
ami: "ami-005e54dee72cc1d00",
instanceType: aws.ec2.InstanceType.T2_Micro,
primaryNetworkInterface: {
networkInterfaceId: example.id,
},
creditSpecification: {
cpuCredits: "unlimited",
},
});
import pulumi
import pulumi_aws as aws
my_vpc = aws.ec2.Vpc("my_vpc",
cidr_block="172.16.0.0/16",
tags={
"Name": "tf-example",
})
my_subnet = aws.ec2.Subnet("my_subnet",
vpc_id=my_vpc.id,
cidr_block="172.16.10.0/24",
availability_zone="us-west-2a",
tags={
"Name": "tf-example",
})
example = aws.ec2.NetworkInterface("example",
subnet_id=my_subnet.id,
private_ips=["172.16.10.100"],
tags={
"Name": "primary_network_interface",
})
example_instance = aws.ec2.Instance("example",
ami="ami-005e54dee72cc1d00",
instance_type=aws.ec2.InstanceType.T2_MICRO,
primary_network_interface={
"network_interface_id": example.id,
},
credit_specification={
"cpu_credits": "unlimited",
})
package main
import (
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ec2"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
myVpc, err := ec2.NewVpc(ctx, "my_vpc", &ec2.VpcArgs{
CidrBlock: pulumi.String("172.16.0.0/16"),
Tags: pulumi.StringMap{
"Name": pulumi.String("tf-example"),
},
})
if err != nil {
return err
}
mySubnet, err := ec2.NewSubnet(ctx, "my_subnet", &ec2.SubnetArgs{
VpcId: myVpc.ID(),
CidrBlock: pulumi.String("172.16.10.0/24"),
AvailabilityZone: pulumi.String("us-west-2a"),
Tags: pulumi.StringMap{
"Name": pulumi.String("tf-example"),
},
})
if err != nil {
return err
}
example, err := ec2.NewNetworkInterface(ctx, "example", &ec2.NetworkInterfaceArgs{
SubnetId: mySubnet.ID(),
PrivateIps: pulumi.StringArray{
pulumi.String("172.16.10.100"),
},
Tags: pulumi.StringMap{
"Name": pulumi.String("primary_network_interface"),
},
})
if err != nil {
return err
}
_, err = ec2.NewInstance(ctx, "example", &ec2.InstanceArgs{
Ami: pulumi.String("ami-005e54dee72cc1d00"),
InstanceType: pulumi.String(ec2.InstanceType_T2_Micro),
PrimaryNetworkInterface: &ec2.InstancePrimaryNetworkInterfaceArgs{
NetworkInterfaceId: example.ID(),
},
CreditSpecification: &ec2.InstanceCreditSpecificationArgs{
CpuCredits: pulumi.String("unlimited"),
},
})
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;
return await Deployment.RunAsync(() =>
{
var myVpc = new Aws.Ec2.Vpc("my_vpc", new()
{
CidrBlock = "172.16.0.0/16",
Tags =
{
{ "Name", "tf-example" },
},
});
var mySubnet = new Aws.Ec2.Subnet("my_subnet", new()
{
VpcId = myVpc.Id,
CidrBlock = "172.16.10.0/24",
AvailabilityZone = "us-west-2a",
Tags =
{
{ "Name", "tf-example" },
},
});
var example = new Aws.Ec2.NetworkInterface("example", new()
{
SubnetId = mySubnet.Id,
PrivateIps = new[]
{
"172.16.10.100",
},
Tags =
{
{ "Name", "primary_network_interface" },
},
});
var exampleInstance = new Aws.Ec2.Instance("example", new()
{
Ami = "ami-005e54dee72cc1d00",
InstanceType = Aws.Ec2.InstanceType.T2_Micro,
PrimaryNetworkInterface = new Aws.Ec2.Inputs.InstancePrimaryNetworkInterfaceArgs
{
NetworkInterfaceId = example.Id,
},
CreditSpecification = new Aws.Ec2.Inputs.InstanceCreditSpecificationArgs
{
CpuCredits = "unlimited",
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.ec2.Vpc;
import com.pulumi.aws.ec2.VpcArgs;
import com.pulumi.aws.ec2.Subnet;
import com.pulumi.aws.ec2.SubnetArgs;
import com.pulumi.aws.ec2.NetworkInterface;
import com.pulumi.aws.ec2.NetworkInterfaceArgs;
import com.pulumi.aws.ec2.Instance;
import com.pulumi.aws.ec2.InstanceArgs;
import com.pulumi.aws.ec2.inputs.InstancePrimaryNetworkInterfaceArgs;
import com.pulumi.aws.ec2.inputs.InstanceCreditSpecificationArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) {
Pulumi.run(App::stack);
}
public static void stack(Context ctx) {
var myVpc = new Vpc("myVpc", VpcArgs.builder()
.cidrBlock("172.16.0.0/16")
.tags(Map.of("Name", "tf-example"))
.build());
var mySubnet = new Subnet("mySubnet", SubnetArgs.builder()
.vpcId(myVpc.id())
.cidrBlock("172.16.10.0/24")
.availabilityZone("us-west-2a")
.tags(Map.of("Name", "tf-example"))
.build());
var example = new NetworkInterface("example", NetworkInterfaceArgs.builder()
.subnetId(mySubnet.id())
.privateIps("172.16.10.100")
.tags(Map.of("Name", "primary_network_interface"))
.build());
var exampleInstance = new Instance("exampleInstance", InstanceArgs.builder()
.ami("ami-005e54dee72cc1d00")
.instanceType("t2.micro")
.primaryNetworkInterface(InstancePrimaryNetworkInterfaceArgs.builder()
.networkInterfaceId(example.id())
.build())
.creditSpecification(InstanceCreditSpecificationArgs.builder()
.cpuCredits("unlimited")
.build())
.build());
}
}
resources:
myVpc:
type: aws:ec2:Vpc
name: my_vpc
properties:
cidrBlock: 172.16.0.0/16
tags:
Name: tf-example
mySubnet:
type: aws:ec2:Subnet
name: my_subnet
properties:
vpcId: ${myVpc.id}
cidrBlock: 172.16.10.0/24
availabilityZone: us-west-2a
tags:
Name: tf-example
example:
type: aws:ec2:NetworkInterface
properties:
subnetId: ${mySubnet.id}
privateIps:
- 172.16.10.100
tags:
Name: primary_network_interface
exampleInstance:
type: aws:ec2:Instance
name: example
properties:
ami: ami-005e54dee72cc1d00
instanceType: t2.micro
primaryNetworkInterface:
networkInterfaceId: ${example.id}
creditSpecification:
cpuCredits: unlimited
The example creates a VPC, subnet, and network interface with a specific private IP, then attaches it as the primary interface using primaryNetworkInterface. This gives you control over network identity and attachment order. The creditSpecification block configures CPU credit behavior for burstable instance types.
Configure CPU cores and threads for specialized workloads
Certain workloads benefit from custom CPU topology to optimize performance or meet software licensing requirements based on core counts.
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const example = new aws.ec2.Vpc("example", {
cidrBlock: "172.16.0.0/16",
tags: {
Name: "tf-example",
},
});
const exampleSubnet = new aws.ec2.Subnet("example", {
vpcId: example.id,
cidrBlock: "172.16.10.0/24",
availabilityZone: "us-east-2a",
tags: {
Name: "tf-example",
},
});
const amzn_linux_2023_ami = aws.ec2.getAmi({
mostRecent: true,
owners: ["amazon"],
filters: [{
name: "name",
values: ["al2023-ami-2023.*-x86_64"],
}],
});
const exampleInstance = new aws.ec2.Instance("example", {
ami: amzn_linux_2023_ami.then(amzn_linux_2023_ami => amzn_linux_2023_ami.id),
instanceType: aws.ec2.InstanceType.C6a_2XLarge,
subnetId: exampleSubnet.id,
cpuOptions: {
coreCount: 2,
threadsPerCore: 2,
},
tags: {
Name: "tf-example",
},
});
import pulumi
import pulumi_aws as aws
example = aws.ec2.Vpc("example",
cidr_block="172.16.0.0/16",
tags={
"Name": "tf-example",
})
example_subnet = aws.ec2.Subnet("example",
vpc_id=example.id,
cidr_block="172.16.10.0/24",
availability_zone="us-east-2a",
tags={
"Name": "tf-example",
})
amzn_linux_2023_ami = aws.ec2.get_ami(most_recent=True,
owners=["amazon"],
filters=[{
"name": "name",
"values": ["al2023-ami-2023.*-x86_64"],
}])
example_instance = aws.ec2.Instance("example",
ami=amzn_linux_2023_ami.id,
instance_type=aws.ec2.InstanceType.C6A_2_X_LARGE,
subnet_id=example_subnet.id,
cpu_options={
"core_count": 2,
"threads_per_core": 2,
},
tags={
"Name": "tf-example",
})
package main
import (
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ec2"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
example, err := ec2.NewVpc(ctx, "example", &ec2.VpcArgs{
CidrBlock: pulumi.String("172.16.0.0/16"),
Tags: pulumi.StringMap{
"Name": pulumi.String("tf-example"),
},
})
if err != nil {
return err
}
exampleSubnet, err := ec2.NewSubnet(ctx, "example", &ec2.SubnetArgs{
VpcId: example.ID(),
CidrBlock: pulumi.String("172.16.10.0/24"),
AvailabilityZone: pulumi.String("us-east-2a"),
Tags: pulumi.StringMap{
"Name": pulumi.String("tf-example"),
},
})
if err != nil {
return err
}
amzn_linux_2023_ami, err := ec2.LookupAmi(ctx, &ec2.LookupAmiArgs{
MostRecent: pulumi.BoolRef(true),
Owners: []string{
"amazon",
},
Filters: []ec2.GetAmiFilter{
{
Name: "name",
Values: []string{
"al2023-ami-2023.*-x86_64",
},
},
},
}, nil)
if err != nil {
return err
}
_, err = ec2.NewInstance(ctx, "example", &ec2.InstanceArgs{
Ami: pulumi.String(amzn_linux_2023_ami.Id),
InstanceType: pulumi.String(ec2.InstanceType_C6a_2XLarge),
SubnetId: exampleSubnet.ID(),
CpuOptions: &ec2.InstanceCpuOptionsArgs{
CoreCount: pulumi.Int(2),
ThreadsPerCore: pulumi.Int(2),
},
Tags: pulumi.StringMap{
"Name": pulumi.String("tf-example"),
},
})
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;
return await Deployment.RunAsync(() =>
{
var example = new Aws.Ec2.Vpc("example", new()
{
CidrBlock = "172.16.0.0/16",
Tags =
{
{ "Name", "tf-example" },
},
});
var exampleSubnet = new Aws.Ec2.Subnet("example", new()
{
VpcId = example.Id,
CidrBlock = "172.16.10.0/24",
AvailabilityZone = "us-east-2a",
Tags =
{
{ "Name", "tf-example" },
},
});
var amzn_linux_2023_ami = Aws.Ec2.GetAmi.Invoke(new()
{
MostRecent = true,
Owners = new[]
{
"amazon",
},
Filters = new[]
{
new Aws.Ec2.Inputs.GetAmiFilterInputArgs
{
Name = "name",
Values = new[]
{
"al2023-ami-2023.*-x86_64",
},
},
},
});
var exampleInstance = new Aws.Ec2.Instance("example", new()
{
Ami = amzn_linux_2023_ami.Apply(amzn_linux_2023_ami => amzn_linux_2023_ami.Apply(getAmiResult => getAmiResult.Id)),
InstanceType = Aws.Ec2.InstanceType.C6a_2XLarge,
SubnetId = exampleSubnet.Id,
CpuOptions = new Aws.Ec2.Inputs.InstanceCpuOptionsArgs
{
CoreCount = 2,
ThreadsPerCore = 2,
},
Tags =
{
{ "Name", "tf-example" },
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.ec2.Vpc;
import com.pulumi.aws.ec2.VpcArgs;
import com.pulumi.aws.ec2.Subnet;
import com.pulumi.aws.ec2.SubnetArgs;
import com.pulumi.aws.ec2.Ec2Functions;
import com.pulumi.aws.ec2.inputs.GetAmiArgs;
import com.pulumi.aws.ec2.Instance;
import com.pulumi.aws.ec2.InstanceArgs;
import com.pulumi.aws.ec2.inputs.InstanceCpuOptionsArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) {
Pulumi.run(App::stack);
}
public static void stack(Context ctx) {
var example = new Vpc("example", VpcArgs.builder()
.cidrBlock("172.16.0.0/16")
.tags(Map.of("Name", "tf-example"))
.build());
var exampleSubnet = new Subnet("exampleSubnet", SubnetArgs.builder()
.vpcId(example.id())
.cidrBlock("172.16.10.0/24")
.availabilityZone("us-east-2a")
.tags(Map.of("Name", "tf-example"))
.build());
final var amzn-linux-2023-ami = Ec2Functions.getAmi(GetAmiArgs.builder()
.mostRecent(true)
.owners("amazon")
.filters(GetAmiFilterArgs.builder()
.name("name")
.values("al2023-ami-2023.*-x86_64")
.build())
.build());
var exampleInstance = new Instance("exampleInstance", InstanceArgs.builder()
.ami(amzn_linux_2023_ami.id())
.instanceType("c6a.2xlarge")
.subnetId(exampleSubnet.id())
.cpuOptions(InstanceCpuOptionsArgs.builder()
.coreCount(2)
.threadsPerCore(2)
.build())
.tags(Map.of("Name", "tf-example"))
.build());
}
}
resources:
example:
type: aws:ec2:Vpc
properties:
cidrBlock: 172.16.0.0/16
tags:
Name: tf-example
exampleSubnet:
type: aws:ec2:Subnet
name: example
properties:
vpcId: ${example.id}
cidrBlock: 172.16.10.0/24
availabilityZone: us-east-2a
tags:
Name: tf-example
exampleInstance:
type: aws:ec2:Instance
name: example
properties:
ami: ${["amzn-linux-2023-ami"].id}
instanceType: c6a.2xlarge
subnetId: ${exampleSubnet.id}
cpuOptions:
coreCount: 2
threadsPerCore: 2
tags:
Name: tf-example
variables:
amzn-linux-2023-ami:
fn::invoke:
function: aws:ec2:getAmi
arguments:
mostRecent: true
owners:
- amazon
filters:
- name: name
values:
- al2023-ami-2023.*-x86_64
The cpuOptions block controls CPU topology. The coreCount property sets the number of physical cores, while threadsPerCore controls hyperthreading (1 disables it, 2 enables it). This matters for applications licensed per core or workloads where hyperthreading affects performance.
Beyond These Examples
These snippets focus on specific instance-level features: AMI selection (dynamic filters and SSM Parameter Store), spot instances and cost optimization, and network interface attachment and CPU topology. They’re intentionally minimal rather than full VM deployments.
The examples often use pre-existing infrastructure such as default VPC and subnets (for basic examples), and IAM permissions for SSM Parameter Store. They focus on configuring the instance rather than provisioning everything around it.
To keep things focused, common instance patterns are omitted, including:
- SSH key pairs (keyName) for instance access
- Security groups (vpcSecurityGroupIds)
- User data bootstrapping (userData, userDataBase64)
- EBS volume configuration (ebsBlockDevices, rootBlockDevice)
- IAM instance profiles (iamInstanceProfile)
- Monitoring and metadata options
- Placement groups and tenancy options
These omissions are intentional: the goal is to illustrate how each instance feature is wired, not provide drop-in VM modules. See the EC2 Instance resource reference for all available configuration options.
Frequently Asked Questions
Common Errors & Pitfalls
volumeTags while also managing block device tags outside the instance configuration (such as with aws.ebs.Volume tags or aws.ec2.VolumeAttachment) causes resource cycling and inconsistent behavior. Choose either volumeTags OR external volume tag management, not both.vpcSecurityGroupIds for VPC instances. The securityGroups property is deprecated, doesn’t allow changes without forcing instance replacement, and only works for non-VPC (EC2-Classic) instances.forceDestroy to true requires a successful pulumi up before it takes effect on destroy operations. It won’t work if set in the same operation that replaces or destroys the instance, or immediately after importing an instance. Run pulumi up successfully first, then perform the destroy.publicIp output changes after attaching an Elastic IP to the instance. If you’re using an aws.ec2.Eip, reference the EIP’s address directly instead of the instance’s publicIp output.Immutable Properties & Instance Recreation
ami, availabilityZone, subnetId, keyName, privateIp, tenancy, placementGroup, hostId, instanceMarketOptions (spot), launchTemplate, hibernation, ebsOptimized, associatePublicIpAddress, and several block device properties.enablePrimaryIpv6 is enabled, the primary IPv6 address cannot be disabled. The primary IPv6 address remains until the instance is terminated or the ENI is detached. Disabling enablePrimaryIpv6 after enabling it forces instance recreation.User Data & Configuration Updates
userData for valid UTF-8 strings. Use userDataBase64 for binary data or gzip-compressed content to avoid corruption. By default, updates to either field trigger a stop/start of the instance. Set userDataReplaceOnChange to true if you want updates to trigger a destroy and recreate instead.instanceType and user data updates (by default). Full recreation: immutable properties like ami, availabilityZone, subnetId, keyName, and user data updates if userDataReplaceOnChange is true.AMI & Launch Configuration
resolve:ssm:. For example: ami: 'resolve:ssm:/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64'.ami property overrides any AMI specified in the launch template. Similarly, instanceType overrides the instance type in the launch template. The ami is required unless the launch template specifies one.Networking & Interfaces
networkInterfaces property is deprecated. Use primaryNetworkInterface for the primary interface at launch, and attach additional interfaces using the aws.ec2.NetworkInterfaceAttachment resource.secondaryPrivateIps to assign secondary IPv4 addresses to the primary network interface (eth0). These can only be assigned at instance creation, not to pre-existing network interfaces referenced in a networkInterface block. Check the AWS documentation for the maximum number of IPs per instance type.Spot Instances & Specialized Configurations
instanceMarketOptions with marketType: 'spot' and optionally set spotOptions.maxPrice to specify your maximum price.hostResourceGroupArn to the ARN of your host resource group and either omit the tenancy parameter or set it to host. License Manager will automatically allocate hosts and launch instances based on your configured settings.volumeTags (applied at creation to root and EBS volumes), 4) Root block device tags (root volume only, conflicts with volumeTags), 5) EBS block device tags (specific volume only, conflicts with volumeTags).Ready to get started?
Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.
Create free account