AWS EC2 Instance Terraform module

March 26, 2026 ยท View on GitHub

Terraform module which creates an EC2 instance on AWS.

SWUbanner

Usage

Single EC2 Instance

module "ec2_instance" {
  source  = "terraform-aws-modules/ec2-instance/aws"

  name = "single-instance"

  instance_type = "t3.micro"
  key_name      = "user1"
  monitoring    = true
  subnet_id     = "subnet-eddcdzz4"

  tags = {
    Terraform   = "true"
    Environment = "dev"
  }
}

Multiple EC2 Instance

module "ec2_instance" {
  source  = "terraform-aws-modules/ec2-instance/aws"

  for_each = toset(["one", "two", "three"])

  name = "instance-${each.key}"

  instance_type = "t3.micro"
  key_name      = "user1"
  monitoring    = true
  subnet_id     = "subnet-eddcdzz4"

  tags = {
    Terraform   = "true"
    Environment = "dev"
  }
}

Spot EC2 Instance

module "ec2_instance" {
  source  = "terraform-aws-modules/ec2-instance/aws"

  name = "spot-instance"

  create_spot_instance = true
  spot_price           = "0.60"
  spot_type            = "persistent"

  instance_type = "t3.micro"
  key_name      = "user1"
  monitoring    = true
  subnet_id     = "subnet-eddcdzz4"

  tags = {
    Terraform   = "true"
    Environment = "dev"
  }
}

Examples

Make an encrypted AMI for use

This module does not support encrypted AMI's out of the box however it is easy enough for you to generate one for use

This example creates an encrypted image from the latest ubuntu 20.04 base image.

provider "aws" {
  region = "us-west-2"
}

data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["679593333241"]

  filter {
    name   = "name"
    values = ["ubuntu-minimal/images/hvm-ssd/ubuntu-focal-20.04-*"]
  }

  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }
}

resource "aws_ami_copy" "ubuntu_encrypted_ami" {
  name              = "ubuntu-encrypted-ami"
  description       = "An encrypted root ami based off ${data.aws_ami.ubuntu.id}"
  source_ami_id     = data.aws_ami.ubuntu.id
  source_ami_region = "eu-west-2"
  encrypted         = true

  tags = { Name = "ubuntu-encrypted-ami" }
}

data "aws_ami" "encrypted-ami" {
  most_recent = true

  filter {
    name   = "name"
    values = [aws_ami_copy.ubuntu_encrypted_ami.id]
  }

  owners = ["self"]
}

Conditional creation

The following combinations are supported to conditionally create resources:

module "ec2_instance" {
  source  = "terraform-aws-modules/ec2-instance/aws"

  # Disable creation of EC2 and all resources
  create = false

  # Enable creation of spot instance
  create_spot_instance = true

  # Enable creation of EC2 IAM instance profile
  create_iam_instance_profile = true

  # Disable creation of security group
  create_security_group = false

  # Enable creation of elastic IP
  create_eip = true

  # ... omitted
}

Notes

  • network_interface can't be specified together with vpc_security_group_ids, associate_public_ip_address, subnet_id. See complete example for details.
  • In regards to spot instances, you must grant the AWSServiceRoleForEC2Spot service-linked role access to any custom KMS keys, otherwise your spot request and instances will fail with bad parameters. You can see more details about why the request failed by using the awscli and aws ec2 describe-spot-instance-requests

Requirements

NameVersion
terraform>= 1.5.7
aws>= 6.37

Providers

NameVersion
aws>= 6.37

Modules

No modules.

Resources

NameType
aws_ebs_volume.thisresource
aws_ec2_tag.spot_instanceresource
aws_eip.thisresource
aws_iam_instance_profile.thisresource
aws_iam_role.thisresource
aws_iam_role_policy_attachment.thisresource
aws_instance.ignore_amiresource
aws_instance.thisresource
aws_security_group.thisresource
aws_spot_instance_request.thisresource
aws_volume_attachment.thisresource
aws_vpc_security_group_egress_rule.thisresource
aws_vpc_security_group_ingress_rule.thisresource
aws_iam_policy_document.assume_role_policydata source
aws_partition.currentdata source
aws_ssm_parameter.thisdata source
aws_subnet.thisdata source

Inputs

NameDescriptionTypeDefaultRequired
amiID of AMI to use for the instancestringnullno
ami_ssm_parameterSSM parameter name for the AMI ID. For Amazon Linux AMI SSM parameters see referencestring"/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64"no
associate_public_ip_addressWhether to associate a public IP address with an instance in a VPCboolnullno
availability_zoneAZ to start the instance instringnullno
capacity_reservation_specificationDescribes an instance's Capacity Reservation targeting option
object({
capacity_reservation_preference = optional(string)
capacity_reservation_target = optional(object({
capacity_reservation_id = optional(string)
capacity_reservation_resource_group_arn = optional(string)
}))
})
nullno
cpu_creditsThe credit option for CPU usage (unlimited or standard)stringnullno
cpu_optionsDefines CPU options to apply to the instance at launch time.
object({
amd_sev_snp = optional(string)
core_count = optional(number)
nested_virtualization = optional(string)
threads_per_core = optional(number)
})
nullno
createWhether to create an instancebooltrueno
create_eipDetermines whether a public EIP will be created and associated with the instance.boolfalseno
create_iam_instance_profileDetermines whether an IAM instance profile is created or to use an existing IAM instance profileboolfalseno
create_security_groupDetermines whether a security group will be createdbooltrueno
create_spot_instanceDepicts if the instance is a spot instanceboolfalseno
disable_api_stopIf true, enables EC2 Instance Stop Protectionboolnullno
disable_api_terminationIf true, enables EC2 Instance Termination Protectionboolnullno
ebs_optimizedIf true, the launched EC2 instance will be EBS-optimizedboolnullno
ebs_volumesAdditional EBS volumes to attach to the instance
map(object({
encrypted = optional(bool)
final_snapshot = optional(bool)
iops = optional(number)
kms_key_id = optional(string)
multi_attach_enabled = optional(bool)
outpost_arn = optional(string)
size = optional(number)
snapshot_id = optional(string)
tags = optional(map(string), {})
throughput = optional(number)
type = optional(string, "gp3")
volume_initialization_rate = optional(number)
# Attachment
device_name = optional(string) # Will fall back to use map key as device name
force_detach = optional(bool)
skip_destroy = optional(bool)
stop_instance_before_detaching = optional(bool)
}))
nullno
eip_domainIndicates if this EIP is for use in VPCstring"vpc"no
eip_tagsA map of additional tags to add to the eipmap(string){}no
enable_primary_ipv6Whether to assign a primary IPv6 Global Unicast Address (GUA) to the instance when launched in a dual-stack or IPv6-only subnetboolnullno
enable_volume_tagsWhether to enable volume tags (if enabled it conflicts with root_block_device tags)booltrueno
enclave_options_enabledWhether Nitro Enclaves will be enabled on the instance. Defaults to falseboolnullno
ephemeral_block_deviceCustomize Ephemeral (also known as Instance Store) volumes on the instance
map(object({
device_name = string
no_device = optional(bool)
virtual_name = optional(string)
}))
nullno
force_destroyDestroys instance even if disable_api_termination or disable_api_stop is set to true. Once this parameter is set to true, a successful terraform apply run before a destroy is required to update this value in the resource state. Without a successful terraform apply after this parameter is set, this flag will have no effect. If setting this field in the same operation that would require replacing the instance or destroying the instance, this flag will not work. Additionally when importing an instance, a successful terraform apply is required to set this value in state before it will take effect on a destroy operation.boolnullno
get_password_dataIf true, wait for password data to become available and retrieve itboolnullno
hibernationIf true, the launched EC2 instance will support hibernationboolnullno
host_idID of a dedicated host that the instance will be assigned to. Use when an instance is to be launched on a specific dedicated hoststringnullno
host_resource_group_arnARN of the host resource group in which to launch the instances. If you specify an ARN, omit the tenancy parameter or set it to hoststringnullno
iam_instance_profileIAM Instance Profile to launch the instance with. Specified as the name of the Instance Profilestringnullno
iam_role_descriptionDescription of the rolestringnullno
iam_role_nameName to use on IAM role createdstringnullno
iam_role_pathIAM role pathstringnullno
iam_role_permissions_boundaryARN of the policy that is used to set the permissions boundary for the IAM rolestringnullno
iam_role_policiesPolicies attached to the IAM rolemap(string){}no
iam_role_tagsA map of additional tags to add to the IAM role/profile createdmap(string){}no
iam_role_use_name_prefixDetermines whether the IAM role name (iam_role_name or name) is used as a prefixbooltrueno
ignore_ami_changesWhether changes to the AMI ID changes should be ignored by Terraform. Note - changing this value will result in the replacement of the instanceboolfalseno
instance_initiated_shutdown_behaviorShutdown behavior for the instance. Amazon defaults this to stop for EBS-backed instances and terminate for instance-store instances. Cannot be set on instance-store instancestringnullno
instance_market_optionsThe market (purchasing) option for the instance. If set, overrides the create_spot_instance variable
object({
market_type = optional(string)
spot_options = optional(object({
instance_interruption_behavior = optional(string)
max_price = optional(string)
spot_instance_type = optional(string)
valid_until = optional(string)
}))
})
nullno
instance_tagsAdditional tags for the instancemap(string){}no
instance_typeThe type of instance to startstring"t3.micro"no
ipv6_address_countA number of IPv6 addresses to associate with the primary network interface. Amazon EC2 chooses the IPv6 addresses from the range of your subnetnumbernullno
ipv6_addressesSpecify one or more IPv6 addresses from the range of the subnet to associate with the primary network interfacelist(string)nullno
key_nameKey name of the Key Pair to use for the instance; which can be managed using the aws_key_pair resourcestringnullno
launch_templateSpecifies a Launch Template to configure the instance. Parameters configured on this resource will override the corresponding parameters in the Launch Template
object({
id = optional(string)
name = optional(string)
version = optional(string)
})
nullno
maintenance_optionsThe maintenance options for the instance
object({
auto_recovery = optional(string)
})
nullno
metadata_optionsCustomize the metadata options of the instance
object({
http_endpoint = optional(string, "enabled")
http_protocol_ipv6 = optional(string)
http_put_response_hop_limit = optional(number, 1)
http_tokens = optional(string, "required")
instance_metadata_tags = optional(string)
})
{
"http_endpoint": "enabled",
"http_put_response_hop_limit": 1,
"http_tokens": "required"
}
no
monitoringIf true, the launched EC2 instance will have detailed monitoring enabledboolnullno
nameName to be used on EC2 instance createdstring""no
network_interfaceCustomize network interfaces to be attached at instance boot time
map(object({
delete_on_termination = optional(bool)
device_index = optional(number) # Will fall back to use map key as device index
network_card_index = optional(number)
network_interface_id = string
}))
nullno
placement_groupThe Placement Group to start the instance instringnullno
placement_group_idPlacement Group ID to start the instance instringnullno
placement_partition_numberNumber of the partition the instance is in. Valid only if the aws_placement_group resource's strategy argument is set to partitionnumbernullno
private_dns_name_optionsCustomize the private DNS name options of the instance
object({
enable_resource_name_dns_a_record = optional(bool)
enable_resource_name_dns_aaaa_record = optional(bool)
hostname_type = optional(string)
})
nullno
private_ipPrivate IP address to associate with the instance in a VPCstringnullno
putin_khuyloDo you agree that Putin doesn't respect Ukrainian sovereignty and territorial integrity? More info: https://en.wikipedia.org/wiki/Putin_khuylo!booltrueno
regionRegion where the resource(s) will be managed. Defaults to the Region set in the provider configurationstringnullno
root_block_deviceCustomize details about the root block device of the instance. See Block Devices below for details
object({
delete_on_termination = optional(bool)
encrypted = optional(bool)
iops = optional(number)
kms_key_id = optional(string)
tags = optional(map(string))
throughput = optional(number)
size = optional(number)
type = optional(string)
})
nullno
secondary_network_interfaceCustomize secondary network interfaces to be attached to the EC2 instance
map(object({
delete_on_termination = optional(bool)
device_index = optional(number) # Will fall back to use map key as device index
interface_type = optional(string)
network_card_index = number
private_ip_address_count = optional(number)
private_ip_addresses = optional(list(string))
secondary_subnet_id = string
}))
nullno
secondary_private_ipsA list of secondary private IPv4 addresses to assign to the instance's primary network interface (eth0) in a VPC. Can only be assigned to the primary network interface (eth0) attached at instance creation, not a pre-existing network interface i.e. referenced in a network_interface blocklist(string)nullno
security_group_descriptionDescription of the security groupstringnullno
security_group_egress_rulesEgress rules to add to the security group
map(object({
cidr_ipv4 = optional(string)
cidr_ipv6 = optional(string)
description = optional(string)
from_port = optional(number)
ip_protocol = optional(string, "tcp")
prefix_list_id = optional(string)
referenced_security_group_id = optional(string)
tags = optional(map(string), {})
to_port = optional(number)
}))
{
"ipv4_default": {
"cidr_ipv4": "0.0.0.0/0",
"description": "Allow all IPv4 traffic",
"ip_protocol": "-1"
},
"ipv6_default": {
"cidr_ipv6": "::/0",
"description": "Allow all IPv6 traffic",
"ip_protocol": "-1"
}
}
no
security_group_ingress_rulesIngress rules to add to the security group
map(object({
cidr_ipv4 = optional(string)
cidr_ipv6 = optional(string)
description = optional(string)
from_port = optional(number)
ip_protocol = optional(string, "tcp")
prefix_list_id = optional(string)
referenced_security_group_id = optional(string)
tags = optional(map(string), {})
to_port = optional(number)
}))
nullno
security_group_nameName to use on security group createdstringnullno
security_group_tagsA map of additional tags to add to the security group createdmap(string){}no
security_group_use_name_prefixDetermines whether the security group name (security_group_name or name) is used as a prefixbooltrueno
security_group_vpc_idVPC ID to create the security group in. If not set, the security group will be created in the default VPCstringnullno
source_dest_checkControls if traffic is routed to the instance when the destination address does not match the instance. Used for NAT or VPNsboolnullno
spot_instance_interruption_behaviorIndicates Spot instance behavior when it is interrupted. Valid values are terminate, stop, or hibernatestringnullno
spot_launch_groupA launch group is a group of spot instances that launch together and terminate together. If left empty instances are launched and terminated individuallystringnullno
spot_priceThe maximum price to request on the spot market. Defaults to on-demand pricestringnullno
spot_typeIf set to one-time, after the instance is terminated, the spot request will be closed. Default persistentstringnullno
spot_valid_fromThe start date and time of the request, in UTC RFC3339 format(for example, YYYY-MM-DDTHH:MM:SSZ)stringnullno
spot_valid_untilThe end date and time of the request, in UTC RFC3339 format(for example, YYYY-MM-DDTHH:MM:SSZ)stringnullno
spot_wait_for_fulfillmentIf set, Terraform will wait for the Spot Request to be fulfilled, and will throw an error if the timeout of 10m is reachedboolnullno
subnet_idThe VPC Subnet ID to launch instringnullno
tagsA mapping of tags to assign to the resourcemap(string){}no
tenancyThe tenancy of the instance (if the instance is running in a VPC). Available values: default, dedicated, hoststringnullno
timeoutsDefine maximum timeout for creating, updating, and deleting EC2 instance resources
object({
create = optional(string)
update = optional(string)
delete = optional(string)
})
nullno
user_dataThe user data to provide when launching the instance. Do not pass gzip-compressed data via this argument; see user_data_base64 insteadstringnullno
user_data_base64Can be used instead of user_data to pass base64-encoded binary data directly. Use this instead of user_data whenever the value is not a valid UTF-8 string. For example, gzip-encoded user data must be base64-encoded and passed via this argument to avoid corruptionstringnullno
user_data_replace_on_changeWhen used in combination with user_data or user_data_base64 will trigger a destroy and recreate when set to true. Defaults to false if not setboolnullno
volume_tagsA mapping of tags to assign to the devices created by the instance at launch timemap(string){}no
vpc_security_group_idsA list of security group IDs to associate withlist(string)[]no

Outputs

NameDescription
amiAMI ID that was used to create the instance
arnThe ARN of the instance
availability_zoneThe availability zone of the created instance
capacity_reservation_specificationCapacity reservation specification of the instance
ebs_block_deviceEBS block device information
ebs_volumesMap of EBS volumes created and their attributes
ephemeral_block_deviceEphemeral block device information
iam_instance_profile_arnARN assigned by AWS to the instance profile
iam_instance_profile_idInstance profile's ID
iam_instance_profile_uniqueStable and unique string identifying the IAM instance profile
iam_role_arnThe Amazon Resource Name (ARN) specifying the IAM role
iam_role_nameThe name of the IAM role
iam_role_unique_idStable and unique string identifying the IAM role
idThe ID of the instance
instance_stateThe state of the instance
ipv6_addressesThe IPv6 address assigned to the instance, if applicable
outpost_arnThe ARN of the Outpost the instance is assigned to
password_dataBase-64 encoded encrypted password data for the instance. Useful for getting the administrator password for instances running Microsoft Windows. This attribute is only exported if get_password_data is true
primary_network_interface_idThe ID of the instance's primary network interface
private_dnsThe private DNS name assigned to the instance. Can only be used inside the Amazon EC2, and only available if you've enabled DNS hostnames for your VPC
private_ipThe private IP address assigned to the instance
public_dnsThe public DNS name assigned to the instance. For EC2-VPC, this is only available if you've enabled DNS hostnames for your VPC
public_ipThe public IP address assigned to the instance, if applicable.
root_block_deviceRoot block device information
security_group_arnAmazon Resource Name (ARN) of the security group
security_group_idID of the security group
spot_bid_statusThe current bid status of the Spot Instance Request
spot_instance_idThe Instance ID (if any) that is currently fulfilling the Spot Instance request
spot_request_stateThe current request state of the Spot Instance Request
tags_allA map of tags assigned to the resource, including those inherited from the provider default_tags configuration block

Authors

Module is maintained by Anton Babenko with help from these awesome contributors.

License

Apache 2 Licensed. See LICENSE for full details.

Additional information for users from Russia and Belarus