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 typically run in a VPC with subnets and security groups, and may reference network interfaces or require IAM permissions. The examples are intentionally small. Combine them with your own VPC, security groups, SSH keys, and IAM roles.

Launch an instance with dynamic AMI lookup

Most deployments begin by selecting an AMI that matches your 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 filters AMIs by name pattern and virtualization type, returning the most recent match. The owners property restricts results to Canonical’s official Ubuntu images. The ami property references the data source output, ensuring the instance always launches with the latest AMI matching your criteria.

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 data source 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 ami property uses the resolve:ssm: prefix to fetch the AMI ID from Parameter Store at deployment time. This approach provides stable paths to AWS-managed AMI catalogs (like Amazon Linux 2023) without explicit data source configuration. Your AWS 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 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 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 a pre-existing network interface at launch

Some applications require specific IP addresses or network configuration that must be defined before instance launch, such as when migrating workloads or maintaining consistent network identity.

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 primaryNetworkInterface property attaches a pre-created NetworkInterface resource at launch, giving you control over private IPs, security groups, and subnet placement. The creditSpecification property configures CPU credit behavior for burstable instance types (T2, T3, T4g), setting cpuCredits to “unlimited” to allow sustained high CPU usage beyond baseline performance.

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. Setting coreCount to 2 and threadsPerCore to 2 creates a 4-vCPU instance (2 cores × 2 threads) from a C6a.2xlarge that normally provides 8 vCPUs. This configuration can reduce licensing costs for per-core software or optimize performance for workloads that benefit from fewer, faster cores.

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 interface attachment and CPU topology. They’re intentionally minimal rather than full VM deployments.

The examples typically assume pre-existing infrastructure such as VPC, subnets, and security groups, network interfaces (for attachment examples), and IAM permissions for SSM Parameter Store. They focus on configuring the instance rather than provisioning the surrounding infrastructure.

To keep things focused, common instance patterns are omitted, including:

  • SSH key pairs and remote access (keyName)
  • IAM instance profiles for AWS service access
  • User data bootstrapping (userData, userDataBase64)
  • EBS volume configuration (rootBlockDevice, ebsBlockDevices)
  • Security group configuration (vpcSecurityGroupIds)
  • Monitoring, maintenance, and metadata 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.

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 happens when I update userData or userDataBase64?
By default, updates trigger a stop/start of the instance. Set userDataReplaceOnChange to true to trigger destroy/recreate instead. Use userDataBase64 for non-UTF-8 data like gzip-compressed scripts to avoid corruption.
Why isn't forceDestroy working when I try to destroy my instance?
forceDestroy requires a successful pulumi up after setting it to true and before destroying. It won’t work if set in the same operation that replaces or destroys the instance, or immediately after import without running pulumi up first.
What properties force instance replacement when changed?
Many properties are immutable: ami, availabilityZone, ebsOptimized, ephemeralBlockDevices, hostId, instanceMarketOptions, keyName, launchTemplate, placementGroup, placementGroupId, placementPartitionNumber, privateDnsNameOptions, privateIp, securityGroups, subnetId, tenancy, and associatePublicIpAddress.
Does changing instanceType replace my instance?
No, updating instanceType triggers a stop/start of the instance, not a replacement.
Networking & Security
Should I use securityGroups or vpcSecurityGroupIds?
Use vpcSecurityGroupIds for VPC instances. The securityGroups parameter is deprecated and forces instance replacement on changes, while vpcSecurityGroupIds allows updates.
Why does my publicIp keep changing after attaching an Elastic IP?
The publicIp attribute reflects the instance’s current public IP, which changes when you attach an EIP. Reference the EIP’s address directly instead of using the instance’s publicIp attribute.
Can I disable enablePrimaryIpv6 after enabling it?
No, once enabled, the first IPv6 GUA becomes the primary IPv6 address and cannot be disabled. Attempting to disable it forces instance recreation.
How do I attach additional network interfaces?
The networkInterface parameter is deprecated. Use primaryNetworkInterface for the primary interface and aws.ec2.NetworkInterfaceAttachment for additional interfaces.
Tagging & Volume Management
Why is using volumeTags causing resource cycling?
Using volumeTags while managing block device tags outside the Instance resource (e.g., via aws.ebs.Volume tags with aws.ec2.VolumeAttachment) causes resource cycling and inconsistent behavior. Choose one approach for tag management.
What's the difference between the five types of EC2 instance tags?
  1. Instance tags apply to the instance only, not volumes. 2) Default tags apply to instance and all volumes. 3) Volume tags (volumeTags) apply to root and EBS volumes at creation. 4) Root block device tags apply only to root volume (conflicts with volumeTags). 5) EBS block device tags apply to specific EBS volumes (conflicts with volumeTags).
Dedicated Hosts & Spot Instances
How do I launch an instance on a dedicated host with License Manager?
Set hostResourceGroupArn to your host resource group ARN and either omit tenancy or set it to host.
How do I launch a Spot instance?
Configure instanceMarketOptions with marketType: "spot" and optionally set spotOptions.maxPrice to your maximum price.
AMI Selection
How can I dynamically look up an AMI instead of hardcoding the ID?
Use the aws.ec2.getAmi data source with filters to find AMIs, or reference AWS Systems Manager Parameter Store with the resolve:ssm:/aws/service/ami-amazon-linux-latest/... syntax.

Using a different cloud?

Explore compute guides for other cloud providers: