AWS with Terraform Tutorial: AWS EC2 Instances (12)
How to create AWS EC2 Instances with Terraform #
Using the Terraform aws_instance resource block to configure, launch, and secure EC2 instances.
Welcome to our tutorial series where we dive into cloud infrastructure deployment using Terraform or OpenTofu on AWS. In this series, the fundamentals are shown, guiding you through the process of minimizing resource usage and simplifying the deployment complexities associated with cloud infrastructure.
This comprehensive OpenTofu and Terraform tutorial guides you step-by-step through creating infrastructure in AWS using Terraform.
Infrastructure as Code (IaC) helps maintain consistency, enables version control, enhances collaboration among teams, allows for easier replication of environments, streamlines the deployment and management of infrastructure boosting efficiency, and reducing errors in managing complex systems.
Prerequisites #
Read the previous sections of the tutorial, listed in the series index at the end of this page.
AWS EC2 #
Amazon Elastic Compute Cloud (EC2) is a web service offered by Amazon Web Services (AWS) that provides resizable and scalable computing capacity in the cloud. In simple terms, AWS EC2 allows you to launch and manage virtual machines, known as instances, in the AWS cloud.
- AWS offers a variety of instance types optimized for different workloads
- Instances are available in multiple processor architectures (x86 with 64-bit Intel Xeon and AMD EPYC, ARM with 64-bit AWS Graviton processors, and Apple 64-bit M1 and M2 processors).
Configuring an EC2 instance with Terraform requires many of the resources defined in the previous sections of the tutorial:
A Subnet #
AWS Subnets are a subdivision of the IP Network assigned to the VPC. In the AWS with Terraform: The Essential Guide (4/21) – AWS VPC section the VPC was created.
In the section AWS with Terraform: The Essential Guide (5/21) – AWS Subnets four different subnets were defined (two public and two private), whose IDs can be referenced in Terraform using:
- aws_subnet.ditwl-sn-za-pro-pub-00.id
- aws_subnet.ditwl-sn-za-pro-pri-02.id
- aws_subnet.ditwl-sn-zb-pro-pub-04.id
- aws_subnet.ditwl-sn-zb-pro-pri-06.id
The subnets have routing tables assigned that send Internet traffic to either a NAT Gateway or an Internet Gateway, those resources were defined in previous sections of the tutorial:
- AWS with Terraform: The Essential Guide (6/21) – AWS Internet Gateway
- AWS with Terraform: The Essential Guide (7/21) – AWS NAT Gateway
- AWS with Terraform: The Essential Guide (8/21) – AWS Routing Tables
Security groups controlling inbound and outbound traffic to the instance #
An AWS Security Group acts as a virtual firewall for AWS instances and services controlling inbound and outbound traffic. It is a set of rules that filter traffic based on protocols, ports, and origins or destinations.
Three security groups were created in the section AWS with Terraform: The Essential Guide (9/21) – AWS Security Groups:
- ditwl-sg-base-ec2: with common rules to be applied to all instances, allowing inbound connectivity to port TCP 22 from the Internet and also ICMP.
- ditwl-sg-front-end: access rules for the Front End Instance allowing access from the Internet to TCP port 80.
- ditwl-sg-back-end: access rules for the Back-End Instance, allowing access only from the Front-End instance to TCP port 8080.
Security groups in Terraform are referenced using its type, name, and its id property:
- aws_security_group.ditwl-sg-base-ec2.id
- aws_security_group.ditwl-sg-front-end.id
- aws_security_group.ditwl-sg-back-end.id
A Key Pair for performing SSH Public Key Authentication to be injected by Cloud-init #
An AWS Key Pair is a set of security credentials consisting of a public and private key. It securely connects to Amazon EC2 instances and authenticates access using Public key authentication for SSH.
In the section AWS with Terraform: The Essential Guide (10/21) – AWS Key Pairs a Key pair was locally created using ssh-keygen and the Terraform plan was configured to upload the public key to AWS. The public key is referenced in Terraform by its assigned name "ditwl-kp-config-user".
An Amazon Machine Image (AMI) #
The AMI is used as a launching template that includes the operating system (O.S.), root block devices, applications included in the chosen O.S. distribution, and Cloud-init to handle the early initialization of the instance and injection of the assigned public key for user authentication. The AMI architecture needs to be the same as the CPU architecture for the Instance Type. We have chosen to use ARM 64.
The AMI to use has been defined in AWS with Terraform: The Essential Guide (11/21) – AWS AMIs and can be accessed using data.aws_ami.ubuntu-23-04-arm64-minimal.id
An Instance Type #
The instance type defines the hardware (CPU, Memory, Networking, and Storage) that will be used to run the instance.
AWS offers a variety of instance types optimized for different workloads, including general-purpose, compute-optimized, memory-optimized, and GPU instances.
The selection of the right instance type should be made by attending to its expected CPU/Memory/Network usage and the cost and availability of the desired operating system (from the AMI).
This tutorial uses t4g.micro and t4g.small (Arm-based AWS Graviton, burstable).
Definition of EC2 Instances with Terraform #
Previous sections of this AWS with Terraform guide have covered basic information about Terraform and AWS and have configured and used the AWS Terraform provider to create a VPC, four subnets, one Internet Gateway, two NAT Gateways, three Routing Tables, Security Groups, and a Private Key.
Terraform resource "aws_instance" is used to define and launch EC2 Instances. A minimal definition requires the following parameters:
- ami
- instance_type
- subnet_id
- key_name
- vpc_security_group_ids
Additionally, some tags are added to enable classification, filtering, reporting, and integration with other tools like Ansible (see How to use Terraform, AWS, and Ansible Together).
For the tutorial two different EC2 Instances are created:
- A front-end server running Ubuntu 23.04 ARM Minimal, located in a public subnet
- A back-end server running Ubuntu 23.04 ARM Minimal, located in a private subnet
Add the following block to the terraform-aws-tutorial.tf created in previous sections.
# Front-end server running Ubuntu 23.04 ARM Minimal.
resource "aws_instance" "ditwl-ec-front-end-001" {
ami = data.aws_ami.ubuntu-23-04-arm64-minimal.id
instance_type = "t4g.micro"
subnet_id = aws_subnet.ditwl-sn-za-pro-pub-00.id
key_name = "ditwl-kp-config-user"
vpc_security_group_ids = [aws_security_group.ditwl-sg-base-ec2.id, aws_security_group.ditwl-sg-front-end.id]
tags = {
"Name" = "ditwl-ec-front-end-001"
"private_name" = "ditwl-ec-front-end-001"
"public_name" = "www"
"app" = "front-end"
"app_ver" = "2.3"
"os" = "ubuntu"
"os_ver" = "23.04"
"os_arch" = "arm64"
}
}
# Back-end server running Ubuntu 23.04 ARM Minimal.
resource "aws_instance" "ditwl-ec-back-end-123" {
ami = data.aws_ami.ubuntu-23-04-arm64-minimal.id
instance_type = "t4g.small"
subnet_id = aws_subnet.ditwl-sn-za-pro-pri-02.id
key_name = "ditwl-kp-config-user"
vpc_security_group_ids = [aws_security_group.ditwl-sg-base-ec2.id, aws_security_group.ditwl-sg-back-end.id]
tags = {
"Name" = "ditwl-ec-back-end-123"
"private_name" = "ditwl-ec-back-end-123"
"public_name" = "server"
"app" = "back-end"
"app_ver" = "1.2"
"os" = "ubuntu"
"os_ver" = "23.04"
"os_arch" = "arm64"
}
}
Run the Terraform Plan #
Open a command line shell in the directory where the file terraform-aws-tutorial.tf is located, and run the Terraform or OpenTofu plan and apply commands.
Plan using OpenTofu #
Run tofu plan to generate and review the execution plan. Check each line and value to make sure that it corresponds to the desired change.
Terraform will refresh the state by comparing it with the Cloud resources and produce a plan for the resources that need to be created, updated, or destroyed.
In the previous section of the tutorial, some resources were commented out to reduce the infrastructure cost during development. The EC2 instances have dependencies with previous resources, please uncomment all resources to proceed with the Terraform plan.
Run the Terraform plan:
$ tofu plan
data.aws_ami.ubuntu-23-04-arm64-minimal: Reading...
aws_key_pair.ditwl-kp-config-user: Refreshing state... [id=ditwl-kp-config-user]
...
aws_route_table_association.ditwl-rta-za-pro-pri-02: Refreshing state... [id=rtbassoc-0ed81ebdc48d803ca]
aws_main_route_table_association.ditwl-rta-default: Refreshing state... [id=rtbassoc-0de10556495905730]
OpenTofu used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
OpenTofu will perform the following actions:
# aws_instance.ditwl-ec-back-end-123 will be created
+ resource "aws_instance" "ditwl-ec-back-end-123" {
+ ami = "ami-01eba98c26f317c58"
+ instance_type = "t4g.small"
+ key_name = "ditwl-kp-config-user"
+ subnet_id = "subnet-0e89bd0e9d4bf7548"
...
+ tags_all = {
+ "Name" = "ditwl-ec-back-end-123"
+ "app" = "back-end"
+ "app_ver" = "1.2"
+ "cost_center" = "marketing-department"
+ "environment" = "pro"
+ "os" = "ubuntu"
+ "os_arch" = "arm64"
+ "os_ver" = "23.04"
+ "owner" = "IT Wonder Lab"
+ "private_name" = "ditwl-ec-back-end-123"
+ "public_name" = "server"
}
+ vpc_security_group_ids = [
+ "sg-0bd5ea3a5fd1e47dc",
+ "sg-0f6767966ef0f62d6",
]
}
# aws_instance.ditwl-ec-front-end-001 will be created
+ resource "aws_instance" "ditwl-ec-front-end-001" {
+ ami = "ami-01eba98c26f317c58"
+ instance_type = "t4g.micro"
+ key_name = "ditwl-kp-config-user"
+ subnet_id = "subnet-09da811e23c212363"
...
+ tags_all = {
+ "Name" = "ditwl-ec-front-end-001"
+ "app" = "front-end"
+ "app_ver" = "2.3"
+ "cost_center" = "marketing-department"
+ "environment" = "pro"
+ "os" = "ubuntu"
+ "os_arch" = "arm64"
+ "os_ver" = "23.04"
+ "owner" = "IT Wonder Lab"
+ "private_name" = "ditwl-ec-front-end-001"
+ "public_name" = "www"
}
+ vpc_security_group_ids = [
+ "sg-02fc9a9af0e769b28",
+ "sg-0f6767966ef0f62d6",
]
}
Plan: 2 to add, 0 to change, 0 to destroy.
Apply the changes using OpenTofu #
Run tofu apply to generate and apply the execution plan. OpenTofu will generate a new plan and ask for confirmation before applying the changes. Review again the changes and answer yes or no to apply the changes in AWS.
$ tofu apply
data.aws_ami.ubuntu-23-04-arm64-minimal: Reading...
aws_key_pair.ditwl-kp-config-user: Refreshing state... [id=ditwl-kp-config-user]
aws_vpc.ditlw-vpc: Refreshing state... [id=vpc-0361cf67e9e74acf6]
data.aws_ami.ubuntu-23-04-arm64-minimal: Read complete after 2s [id=ami-01eba98c26f317c58]
aws_subnet.ditwl-sn-zb-pro-pri-06: Refreshing state... [id=subnet-0eae7e19db051d338]
...
aws_main_route_table_association.ditwl-rta-default: Refreshing state... [id=rtbassoc-0de10556495905730]
OpenTofu used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
OpenTofu will perform the following actions:
# aws_instance.ditwl-ec-back-end-123 will be created
+ resource "aws_instance" "ditwl-ec-back-end-123" {
+ ami = "ami-01eba98c26f317c58"
...
+ instance_type = "t4g.small"
+ key_name = "ditwl-kp-config-user"
+ subnet_id = "subnet-09da811e23c212363"
+ vpc_security_group_ids = [
+ "sg-0bd5ea3a5fd1e47dc",
+ "sg-0f6767966ef0f62d6",
]
}
# aws_instance.ditwl-ec-front-end-001 will be created
+ resource "aws_instance" "ditwl-ec-front-end-001" {
+ ami = "ami-01eba98c26f317c58"
...
+ key_name = "ditwl-kp-config-user"
+ subnet_id = "subnet-09da811e23c212363"
...
+ vpc_security_group_ids = [
+ "sg-02fc9a9af0e769b28",
+ "sg-0f6767966ef0f62d6",
]
}
Plan: 2 to add, 0 to change, 0 to destroy.
Do you want to perform these actions?
OpenTofu will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
aws_instance.ditwl-ec-back-end-123: Creating...
aws_instance.ditwl-ec-front-end-001: Creating...
aws_instance.ditwl-ec-back-end-123: Still creating... [10s elapsed]
aws_instance.ditwl-ec-front-end-001: Still creating... [10s elapsed]
aws_instance.ditwl-ec-back-end-123: Creation complete after 16s [id=i-0c4927b77237710fb]
aws_instance.ditwl-ec-front-end-001: Creation complete after 16s [id=i-0286e96ae119c8391]
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
Review the result, it should be 2 (resource) added, 0 changed, 0 destroyed.
You can check that the EC2 instances have been created in the AWS Console.
Navigate to AWS Console: EC2 – Instances.

Destroy the infrastructure using OpenTofu #
The time that the infrastructure is running will be billed by AWS. To reduce the cost during Terraform development or when the infrastructure is not needed anymore terraform destroy will be used.
Terraform destroy terminates all the resources created by the plan.
Run tofu destroy to generate and apply the destruction plan. OpenTofu will generate a new plan with the resources to terminate and ask for confirmation before applying the changes.
All data will be lost but you will be able to recreate the infrastructure when needed using tofu apply.
$ tofu destroy
data.aws_ami.ubuntu-23-04-arm64-minimal: Reading...
aws_key_pair.ditwl-kp-config-user: Refreshing state... [id=ditwl-kp-config-user]
....
aws_main_route_table_association.ditwl-rta-default: Refreshing state... [id=rtbassoc-0de10556495905730]
OpenTofu used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
- destroy
OpenTofu will perform the following actions:
# aws_instance.ditwl-ec-back-end-123 will be destroyed
- resource "aws_instance" "ditwl-ec-back-end-123" {
- ami = "ami-01eba98c26f317c58" -> null
- id = "i-0d337cae95cba251d" -> null
}
...
# aws_route_table_association.ditwl-rta-zb-pro-pri-06 will be destroyed
- resource "aws_route_table_association" "ditwl-rta-zb-pro-pri-06" {
- id = "rtbassoc-0de046c1296c9483a" -> null
- route_table_id = "rtb-0f9a668ee7cc5fb83" -> null
- subnet_id = "subnet-0eae7e19db051d338" -> null
}
...
# aws_subnet.ditwl-sn-zb-pro-pub-04 will be destroyed
- resource "aws_subnet" "ditwl-sn-zb-pro-pub-04" {
...
}
# aws_vpc.ditlw-vpc will be destroyed
- resource "aws_vpc" "ditlw-vpc" {
...
}
Plan: 0 to add, 0 to change, 23 to destroy.
Do you really want to destroy all resources?
OpenTofu will destroy all your managed infrastructure, as shown above.
There is no undo. Only 'yes' will be accepted to confirm.
Enter a value: yes
aws_route_table_association.ditwl-rta-zb-pro-pri-06: Destroying... [id=rtbassoc-0de046c1296c9483a]
aws_subnet.ditwl-sn-zb-pro-pub-04: Destroying... [id=subnet-03d11ad36cc9e13d8]
...
aws_subnet.ditwl-sn-za-pro-pub-00: Destroying... [id=subnet-09da811e23c212363]
aws_security_group.ditwl-sg-front-end: Destroying... [id=sg-02fc9a9af0e769b28]
aws_security_group.ditwl-sg-base-ec2: Destroying... [id=sg-0f6767966ef0f62d6]
aws_subnet.ditwl-sn-za-pro-pri-02: Destruction complete after 1s
aws_subnet.ditwl-sn-za-pro-pub-00: Destruction complete after 1s
aws_security_group.ditwl-sg-back-end: Destruction complete after 2s
aws_security_group.ditwl-sg-front-end: Destruction complete after 2s
aws_security_group.ditwl-sg-base-ec2: Destruction complete after 2s
aws_vpc.ditlw-vpc: Destroying... [id=vpc-0361cf67e9e74acf6]
aws_vpc.ditlw-vpc: Destruction complete after 4s
Destroy complete! Resources: 29 destroyed.
AWS EC2 Cost #
EC2 instances have different prices depending on their type, region, and purchase model (on-demand, saving plan, or spot). Check Amazon EC2 Pricing.
Common Questions About AWS EC2 #
What are some advantages of using Terraform for provisioning EC2 resources compared to manual methods? #
- Infrastructure as Code (IaC): Terraform allows you to define your infrastructure as code, which means you can version control, review, and collaborate on infrastructure changes just like you do with application code. This ensures consistency and repeatability in your infrastructure deployments.
- Automation: Terraform automates the provisioning process, reducing the likelihood of human errors that can occur with manual configurations. This leads to more reliable and consistent infrastructure deployments.
- Ease of Management: With Terraform, you can manage your entire infrastructure stack, including EC2 instances, security groups, IAM roles, and other resources, in a single configuration file. This simplifies management and reduces the need for manual intervention.
- Resource Tracking and Dependency Management: Terraform automatically tracks dependencies between resources, ensuring that they are created, updated, or destroyed in the correct order. This helps prevent issues such as resource conflicts or missing dependencies.
- Scalability: Terraform makes it easy to scale your infrastructure up or down based on demand by simply adjusting the configuration file. You can quickly add or remove EC2 instances, update instance types, or modify other parameters as needed.
- Integration with Other Tools: Terraform integrates seamlessly with other DevOps tools and workflows, such as version control systems (e.g., Git), continuous integration/continuous deployment (CI/CD) pipelines, and configuration management tools. This allows you to incorporate infrastructure changes into your existing workflows without disruption.
How does Terraform handle updates and changes to EC2 instances? #
Changes in Terraform EC2 instance definition (e.g., modifying instance type, adding tags, updating security group rules) are detected by Terraform during the planning phase.
The current state of the infrastructure (as tracked in the Terraform state file) is compared with the desired state defined in the configuration and a plan is generated.
In some cases, changes may require replacing existing EC2 instances rather than updating them in place. This typically happens when changes are not compatible with in-place updates (e.g., changing the AMI or the subnet). Changing the instance type is done in place: the instance is stopped, modified and started again. By default, when a replacement is needed Terraform destroys the old instance and then creates the new one; add lifecycle { create_before_destroy = true } to create the new instance first.
Terraform ensures that changes to EC2 instances are applied in the correct order, taking into account dependencies between resources.
Can Terraform manage auto-scaling groups and load balancers alongside EC2 instances? #
Yes, Terraform can manage auto-scaling groups (resource "aws_autoscaling_group") and load balancers (resource "aws_lb") alongside EC2 instances. See AWS Auto Scaling and AWS Load Balancers later in the series.
Does Terraform support multi-region deployments of EC2 instances? #
Yes, by using a different AWS provider (with an alias) for each region.
Next Steps #
Continue with AWS RDS, or see the full series index.