Launch an EC2 Instance on AWS

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
Why am I getting resource cycling when using volumeTags?
Using 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.
Should I use securityGroups or vpcSecurityGroupIds for my VPC instance?
Use 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.
Why isn't forceDestroy working to bypass termination protection?
Setting 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.
Why does my publicIp value change after attaching an Elastic IP?
The 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
What changes force my EC2 instance to be recreated?
These properties are immutable and force instance recreation when changed: ami, availabilityZone, subnetId, keyName, privateIp, tenancy, placementGroup, hostId, instanceMarketOptions (spot), launchTemplate, hibernation, ebsOptimized, associatePublicIpAddress, and several block device properties.
Can I disable IPv6 after enabling enablePrimaryIpv6?
No. Once 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
What's the difference between userData and userDataBase64, and when should I use each?
Use 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.
What triggers a stop/start versus a full recreation of my instance?
Stop/start: 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
Can I use an AMI from AWS Systems Manager Parameter Store?
Yes, prefix the parameter path with resolve:ssm:. For example: ami: 'resolve:ssm:/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64'.
What happens if I specify both ami and launchTemplate?
The 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
How do I attach multiple network interfaces to my instance?
The networkInterfaces property is deprecated. Use primaryNetworkInterface for the primary interface at launch, and attach additional interfaces using the aws.ec2.NetworkInterfaceAttachment resource.
Can I assign secondary private IPs to my instance?
Yes, use 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
How do I launch a spot instance?
Configure instanceMarketOptions with marketType: 'spot' and optionally set spotOptions.maxPrice to specify your maximum price.
How do I use a dedicated host with License Manager?
Set 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.
What are the different types of tags I can apply to an EC2 instance and its volumes?
There are five tag types: 1) Instance tags (applied to instance only), 2) Default tags (applied to instance and all volumes), 3) Volume tags via 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