Launch an EC2 Instance on AWS

The aws:ec2/instance:Instance resource, part of the Pulumi AWS provider, defines an EC2 virtual machine instance: its AMI, instance type, networking, and compute options. This guide focuses on four capabilities: dynamic AMI selection, spot instance requests, network interface attachment, and CPU topology configuration.

Instances require an AMI and run in a VPC with subnets and security groups. Most examples use the default VPC; some create VPC infrastructure inline. The examples are intentionally small. Combine them with your own VPC, security groups, SSH keys, and user data scripts.

Launch an instance with dynamic AMI lookup

Most deployments begin by selecting an AMI that matches OS and architecture requirements. Dynamic AMI lookup 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 property narrows results by name pattern and virtualization type, while owners restricts results to trusted publishers (099720109477 is Canonical’s AWS account). The ami property references the data source 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 that update automatically.

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 ami property uses the resolve:ssm: prefix to fetch AMI IDs from Parameter Store at deployment time. This approach provides stable parameter paths that AWS maintains, eliminating the need for custom AMI lookup logic. Your IAM credentials must have permissions to read SSM parameters.

Request spot instances with price limits

Workloads that can tolerate interruption often use spot instances to reduce compute costs by bidding on spare 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 configures spot instance behavior. Setting marketType to “spot” requests spare capacity, while maxPrice caps your bid. When spot prices exceed your limit or capacity becomes unavailable, AWS terminates the instance. The example doesn’t configure spotInstanceInterruptionBehavior, so AWS defaults to terminating the instance on interruption.

Attach custom network interfaces at launch

Some applications require specific IP addresses or network configuration that must be defined at launch time rather than using default VPC networking.

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, then attaches the interface to the instance via primaryNetworkInterface. The networkInterfaceId property references the pre-created interface, giving you control over private IP addresses and network placement. This approach is useful when you need consistent IP addresses across instance replacements.

Configure CPU cores and threads for specialized workloads

Certain workloads benefit from custom CPU topology to optimize for performance or meet software licensing requirements that charge per core.

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 property controls CPU topology. The coreCount property sets the number of physical cores, while threadsPerCore controls hyperthreading (1 disables it, 2 enables it). This example configures a c6a.2xlarge instance with 2 cores and 2 threads per core, totaling 4 vCPUs. Some software licenses charge per core rather than per vCPU, making this configuration valuable for cost optimization.

Beyond these examples

These snippets focus on specific instance-level features: AMI selection (dynamic filters and SSM Parameter Store), spot instances and pricing, and network interfaces and CPU topology. They’re intentionally minimal rather than full VM deployments.

The examples may reference pre-existing infrastructure such as default VPC and subnets (for examples 1-3), 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 and security groups
  • IAM instance profiles (iamInstanceProfile)
  • User data bootstrapping (userData, userDataBase64)
  • EBS volume configuration (rootBlockDevice, ebsBlockDevices)
  • Placement groups and tenancy options
  • Metadata options and monitoring configuration

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.

Let's launch an EC2 Instance

Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.

Try Pulumi Cloud for FREE

Frequently Asked Questions

Instance Lifecycle & Updates
What changes trigger a stop/start of my EC2 instance?
Updates to instanceType, userData, or userDataBase64 trigger a stop/start by default. If userDataReplaceOnChange is set to true, user data changes will instead trigger a destroy and recreate.
Why isn't forceDestroy working when I try to destroy my instance?
Setting forceDestroy to true requires a successful pulumi up run before it takes effect. It won’t work if set in the same operation that replaces or destroys the instance, or immediately after import. Run pulumi up successfully first, then perform the destroy.
What properties can't be changed after instance creation?
Many properties are immutable, including ami, availabilityZone, subnetId, keyName, tenancy, privateIp, ebsOptimized, hibernation, placementGroup, and instanceMarketOptions. Changes to these properties force instance replacement.
Networking & Security
Should I use securityGroups or vpcSecurityGroupIds?
Use vpcSecurityGroupIds for VPC instances. The securityGroups property is deprecated because it doesn’t allow updates and forces instance replacement if changed. vpcSecurityGroupIds allows updates without replacement.
Why does my instance's publicIp change after attaching an Elastic IP?
The publicIp attribute changes after attaching an aws.ec2.Eip. Refer to the EIP’s address directly instead of using the instance’s publicIp attribute to avoid broken references.
Can I disable enablePrimaryIpv6 after enabling it?
No. Once enablePrimaryIpv6 is enabled, the first IPv6 GUA becomes the primary address and cannot be disabled. Attempting to disable it forces instance recreation. The primary IPv6 address remains until the instance is terminated or the ENI is detached.
How do I use a pre-existing network interface?
Configure primaryNetworkInterface with the network interface ID. The networkInterfaces property is deprecated; use aws.ec2.NetworkInterfaceAttachment for additional interfaces.
User Data & Configuration
How do I handle gzip-compressed user data?
Use userDataBase64 instead of userData for gzip-compressed or binary data. Passing gzip-compressed data via userData causes corruption because it’s not valid UTF-8.
What's the difference between userData updates causing stop/start vs replacement?
By default, userData or userDataBase64 updates trigger a stop/start. Set userDataReplaceOnChange to true to trigger a destroy and recreate instead.
Block Devices & Tagging
Why is using volumeTags causing resource cycling?
Using volumeTags while managing block device tags outside aws.ec2.Instance (e.g., via aws.ebs.Volume tags attached with aws.ec2.VolumeAttachment) results in resource cycling and inconsistent behavior. Don’t use volumeTags if you plan to manage volume tags externally.
What are the different types of tags for EC2 instances and volumes?
There are five types: (1) instance tags (instance only), (2) provider defaultTags (instance + volumes), (3) volumeTags (volumes at creation), (4) rootBlockDevice.tags (root volume only), and (5) ebsBlockDevice.tags (specific EBS volume). The last two conflict with volumeTags.
AMI & Launch Configuration
How do I specify an AMI for my instance?
Set the ami property (required unless launchTemplate is specified). You can use a data source lookup, or reference AWS Systems Manager Parameter Store with the syntax resolve:ssm:/aws/service/ami-amazon-linux-latest/....
How do I launch a Spot instance?
Configure instanceMarketOptions with marketType set to "spot" and optionally set spotOptions.maxPrice to specify your maximum price.
How do I launch an instance on a dedicated host?
Set hostResourceGroupArn to the ARN of your host resource group and either omit tenancy or set it to "host". Valid tenancy values are "default", "dedicated", and "host".

Using a different cloud?

Explore compute guides for other cloud providers: