Terraform Course - Automate your AWS cloud infrastructure

Table of contents
- 1. AWS Setup
- 2. Windows Setup
- 3. MacOS Setup
- 4. Linux Install
- 5. VS Code
- 6. Essential Terraform Commands (Must Know)
- 7. Terraform Configuration: Provisioning EC2 Instance on AWS
- 8. Provisioning a VPC and Subnet in AWS Using Terraform
- 9. TERRAFORM PRACTICE PROJECT — Full Infrastructure Deployment on AWS
- Provider Block
- Create VPC
- Create Internet Gateway
- Create Custom Route Table
- Create Subnet
- Associate Route Table with Subnet
- Create Security Group (Allow Port 22, 80, 443)
- Create Network Interface
- Assign Elastic IP to ENI
- Create Ubuntu EC2 Instance + Install Apache2
- Final Outcome
- Terraform Commands to Run
- ⚠️ Important Notes
- 10. Breakdown of Each Output Variable
- 11. Terraform Target Resource
- 12. Terraform Variables
1. AWS Setup
Creating AWS account
Accessing AWS Console
Generating Access Keys (for later Terraform use)
Setting IAM permissions (if needed)
Security recommendations
2. Windows Setup
Downloading Terraform binary
Adding Terraform to system
PATH
Verifying installation
Testing with a simple
terraform version
command
3. MacOS Setup
Installing via Homebrew (recommended)
Manual download method (optional)
Setting up environment variables
Verifying installation
4. Linux Install
Ubuntu/Debian: Install via
apt
Fedora/RedHat: Install via
dnf
or manual methodSetting path and permissions
Verifying installation
5. VS Code
Installing VS Code
Installing Terraform extension (
HashiCorp Terraform
)Recommended extensions (YAML, AWS Toolkit)
Syntax highlighting, IntelliSense
VS Code + Terminal integration for Terraform
6. Essential Terraform Commands (Must Know)
Before Terraform can provision infrastructure, it goes through a specific workflow. Here are the most essential commands used in this lifecycle:
Command | Purpose |
terraform init | Initializes the Terraform working directory and downloads the required provider plugins (like AWS). |
terraform validate | Validates whether the configuration is syntactically correct. |
terraform plan | Generates and displays an execution plan. Shows what Terraform will do without making any changes. |
terraform apply | Applies the planned changes and provisions the resources on the cloud (e.g., creates EC2). |
terraform destroy | Destroys all the resources defined in the current directory's .tf file. |
Run these commands before starting your work:
# Step 1: Initialize the working directory
terraform init
# Step 2: Validate the configuration syntax
terraform validate
# Step 3: See the execution plan (what Terraform will do)
terraform plan
7. Terraform Configuration: Provisioning EC2 Instance on AWS
Below is a basic Terraform file (main.tf
) that provisions a t2.micro EC2 instance in us-east-1 region.
provider "aws" {
region = "us-east-1"
access_key = "your_access_key"
secret_key = "your_secret_key"
}
resource "aws_instance" "my-first-instance" {
ami = "ami-084568db4383264d4"
instance_type = "t2.micro"
tags = {
Name = "ubuntu"
}
}
Detailed Explanation of the Code
1. Provider Block
provider "aws" {
region = "us-east-1"
access_key = "your_access_key"
secret_key = "your_secret_key"
}
provider "aws"
: Specifies that you're using AWS as your infrastructure provider.region
: AWS region where resources will be deployed. Here it's"us-east-1"
(N. Virginia).access_key
&secret_key
: Credentials to authenticate your AWS account.⚠️ Never hardcode your access keys in production. Use environment variables or AWS profiles instead.
2. Resource Block: aws_instance
resource "aws_instance" "my-first-instance" {
ami = "ami-084568db4383264d4"
instance_type = "t2.micro"
tags = {
Name = "ubuntu"
}
}
resource
: This declares a resource to be created."aws_instance"
: Type of resource (an EC2 instance here)."my-first-instance"
: A local name/reference to this instance.ami
: The Amazon Machine Image ID (Ubuntu Server in this case).instance_type
: Specifies the instance size.t2.micro
is eligible for the AWS Free Tier.tags
: Useful for identification or categorization. The tag here names the instance"ubuntu"
.
Running the Code (Step-by-Step)
Open your terminal or VS Code integrated terminal and follow these steps:
# Step 1: Apply the changes (create EC2 instance)
terraform apply
# Confirm with "yes" when prompted
# Step 2 (Optional): Destroy resources when done
terraform destroy
# Confirm with "yes" to tear down
8. Provisioning a VPC and Subnet in AWS Using Terraform
erraform Configuration
provider "aws" {
region = "us-east-1"
access_key = "your_access_key"
secret_key = "your_secret_key"
}
resource "aws_vpc" "first-vpc" {
cidr_block = "10.0.0.0/16"
tags = {
Name = "production-vpc"
}
}
resource "aws_subnet" "subnet-1" {
vpc_id = aws_vpc.first-vpc.id
cidr_block = "10.0.1.0/24"
tags = {
Name = "production-subnet-1"
}
}
Explanation
🔸 provider "aws"
Block
Same as before — it defines the AWS provider and credentials.
🔸 resource "aws_vpc" "first-vpc"
Block
This block provisions a custom Virtual Private Cloud (VPC):
aws_vpc
: Resource type for creating a VPC.first-vpc
: Local name for the resource, used internally in the configuration.cidr_block = "10.0.0.0/16"
: The IP address range for the VPC.- This means the VPC will support up to 65,536 IP addresses.
tags
: Adds a "Name" tag to help you identify the VPC in AWS Console.
🔸 resource "aws_subnet" "subnet-1"
Block
This creates a subnet inside the VPC:
aws_subnet
: Resource type for creating a subnet.subnet-1
: Local identifier for this subnet.vpc_id = aws_
vpc.first-vpc.id
:This links the subnet to the previously created VPC using its
.id
.Terraform automatically resolves this reference during execution.
cidr_block = "10.0.1.0/24"
: Subnet range (256 IP addresses).tags
: Tags the subnet for easier management and identification.
Commands to Execute
Apply and Provision Infrastructure
terraform apply
Type
yes
when prompted to actually create the VPC and Subnet.
Destroy the Resources (Optional Cleanup)
terraform destroy
This will delete the VPC and the subnet. Again, confirm with
yes
.
9. TERRAFORM PRACTICE PROJECT — Full Infrastructure Deployment on AWS
Provider Block
provider "aws" {
region = "us-east-1"
access_key = "your_access_key"
secret_key = "your_secret_key"
}
This sets up Terraform to use AWS as the provider.
access_key
andsecret_key
authenticate to your AWS account. (⚠️ Never share these publicly.)region
: Defines which region resources will be created in (us-east-1
is N. Virginia).
Create VPC
resource "aws_vpc" "prod-vpc" {
cidr_block = "10.0.0.0/16"
tags = {
Name = "production"
}
}
VPC = Virtual Private Cloud = a logically isolated network in AWS.
cidr_block = "10.0.0.0/16"
creates a large IP range (65536 IPs).tags
: Adds a name to make the VPC identifiable in the AWS console.
Create Internet Gateway
resource "aws_internet_gateway" "gw" {
vpc_id = aws_vpc.prod-vpc.id
}
Provides internet access to resources inside the VPC (like EC2).
Must be attached to a VPC.
Create Custom Route Table
resource "aws_route_table" "prod-route-table" {
vpc_id = aws_vpc.prod-vpc.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.gw.id
}
route {
ipv6_cidr_block = "::/0"
gateway_id = aws_internet_gateway.gw.id
}
tags = {
Name = "Prod"
}
}
Defines how traffic leaves your VPC.
0.0.0.0/0
: All IPv4 traffic goes to Internet Gateway.::/0
: All IPv6 traffic goes to Internet Gateway.Must be associated with a subnet (next step).
Create Subnet
resource "aws_subnet" "subnet-1" {
vpc_id = aws_vpc.prod-vpc.id
cidr_block = "10.0.1.0/24"
availability_zone = "us-east-1a"
tags = {
Name = "prod-subnet"
}
}
A subdivision of your VPC's CIDR block.
10.0.1.0/24
gives 256 usable IPs.Created in AZ
us-east-1a
, ensuring low-latency access between resources in the same zone.
Associate Route Table with Subnet
resource "aws_route_table_association" "a" {
subnet_id = aws_subnet.subnet-1.id
route_table_id = aws_route_table.prod-route-table.id
}
- Links the subnet to the custom route table to enable internet access.
Create Security Group (Allow Port 22, 80, 443)
resource "aws_security_group" "allow_web" {
name = "allow_web_traffic"
description = "Allow Web inbound traffic"
vpc_id = aws_vpc.prod-vpc.id
ingress {
description = "HTTPS"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "HTTP"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "SSH"
from_port = 2
to_port = 2
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "allow_web"
}
}
Defines firewall rules.
Allows:
Port
443
(HTTPS)Port
80
(HTTP)Port
2
egress
rule: allows all outbound traffic.Security group is applied later to the Network Interface.
Create Network Interface
resource "aws_network_interface" "web-server-nic" {
subnet_id = aws_subnet.subnet-1.id
private_ips = ["10.0.1.50"]
security_groups = [aws_security_group.allow_web.id]
}
Creates an Elastic Network Interface (ENI).
ENI is attached to an EC2 instance for communication.
Uses a static private IP (
10.0.1.50
) and a security group.
Assign Elastic IP to ENI
resource "aws_eip" "one" {
vpc = true
network_interface = aws_network_interface.web-server-nic.id
associate_with_private_ip = "10.0.1.50"
depends_on = [ aws_internet_gateway.gw ]
}
Allocates a public IP address (Elastic IP).
Associates it with the ENI, making the EC2 instance publicly reachable.
Depends on IGW to ensure EIP works.
Create Ubuntu EC2 Instance + Install Apache2
resource "aws_instance" "web-server-instance" {
ami = "ami-084568db4383264d4"
instance_type = "t2.micro"
availability_zone = "us-east-1a"
key_name = "firstkey"
network_interface {
device_index = 0
network_interface_id = aws_network_interface.web-server-nic.id
}
user_data = <<-EOF
#!/bin/bash
sudo apt update -y
sudo apt install apache2 -y
sudo systemctl enable apache2
sudo bash -c 'echo your very first web server > /var/www/html/index.html'
EOF
tags = {
Name = "web-server"
}
}
Launches a Ubuntu EC2 instance.
AMI: Official Ubuntu Server AMI.
Instance type:
t2.micro
(Free-tier eligible).Key pair: Required for SSH access. Replace
firstkey
with your key.Attaches the previously created ENI.
user_data
: Shell script runs on boot to:Install Apache2
Enable the service
Create a test web page
Final Outcome
After running terraform apply
:
You’ll have a publicly accessible web server at the Elastic IP.
Visiting it in a browser will show the text:
your very first web server
Terraform Commands to Run
terraform apply # Apply the configuration
Use terraform destroy
if you want to clean up resources.
⚠️ Important Notes
Never share AWS credentials in public files or repositories. Use
.tfvars
or environment variables instead.Make sure the key pair
"firstkey"
exists in AWS EC2 > Key Pairs.
10. Breakdown of Each Output Variable
1. server_public_ip
hclCopyEditoutput "server_public_ip" {
value = aws_eip.one.public_ip
}
Purpose: Displays the Elastic IP (public IP) address assigned to your EC2 instance.
Resource:
aws_
eip.one
Attribute:
.public_ip
Use case: Helps you SSH into the server or open it in a browser (
http://<public_ip>
).
2. server_private_ip
hCopyEditoutput "server_private_ip" {
value = aws_instance.web-server-instance.private_ip
}
Purpose: Displays the private IP assigned to your EC2 instance inside the VPC.
Resource:
aws_instance.web-server-instance
Attribute:
.private_ip
Use case: Useful for internal networking, debugging subnet behavior, or communication within the VPC.
3. server_id
hclCopyEditoutput "server_id" {
value = aws_instance.web-server-instance.id
}
Purpose: Outputs the unique instance ID of your EC2 server.
Example: Looks like
i-0a1b2c3d4e5f6g7h8
.Use case: Useful for referencing the instance in logs, dashboards, or automation scripts.
Usage Example
After running:
terraform apply
You’ll see output like:
Outputs:
server_id = "the_server-id"
server_private_ip = "the_server_private_ip"
server_public_ip = "the_server_public_ip"
11. Terraform Target Resource
In Terraform, targeting a specific resource means applying changes only to that particular resource instead of the whole infrastructure defined in the configuration. This is useful when:
You're debugging or testing a single component.
A specific resource fails and needs to be recreated.
You don’t want to affect other parts of your setup.
Syntax: -target
Option
bashCopyEditterraform apply -target=resource_type.resource_name
Example from Your Practice Project
Assume you have already initialized Terraform:
bashCopyEditterraform init
Now, let’s say you only want to create or update the EC2 instance (aws_instance.web-server-instance
) without applying the whole infrastructure again.
bashCopyEditterraform apply -target=aws_instance.web-server-instance
This will only:
Provision the EC2 instance.
Run
user_data
to install and configure Apache.Attach the previously created network interface.
More Target Examples from Your Code
Resource Description | Terraform Target Command |
Create only the VPC | terraform apply -target=aws_ vpc.prod -vpc |
Create only the Internet Gateway | terraform apply -target=aws_internet_ gateway.gw |
Create only the Route Table | terraform apply -target=aws_route_ table.prod -route-table |
Create only the Security Group | terraform apply -target=aws_security_group.allow_web |
Create only the Elastic IP | terraform apply -target=aws_ eip.one |
Notes & Best Practices
You can combine multiple targets:
bashCopyEditterraform apply -target=aws_vpc.prod-vpc -target=aws_subnet.subnet-1
Targeting is not recommended for regular workflows. It bypasses dependency graph logic and can cause inconsistent states if used carelessly.
Use it mostly for debugging, step-by-step resource creation, or when instructed in CI/CD pipelines.
12. Terraform Variables
What Are Terraform Variables?
Terraform variables allow you to parameterize your configuration. Instead of hardcoding values like CIDR blocks or names, you define variables in one place and reference them throughout your infrastructure code.
This enhances:
Reusability
Flexibility
Readability
Separation of configuration and logic
main.tf
– Code Breakdown
🔹 Variable Declaration
hclCopyEditvariable "subnet_prefix" {
description = "cidr block for the subnet"
# default = "10.0.66.0/24"
}
variable "subnet_prefix"
: Declares a variable.No default is set here, so the value must come from
.tfvars
or CLI input.Expected to be a list of maps (each map with keys
cidr_block
andname
).
VPC Resource
hclCopyEditresource "aws_vpc" "prod-vpc" {
cidr_block = "10.0.0.0/16"
tags = {
Name = "production"
}
}
Creates the base Virtual Private Cloud (VPC).
It will house your subnets and other AWS resources.
Subnet Resources (Dynamic Using Variables)
hclCopyEditresource "aws_subnet" "subnet-1" {
vpc_id = aws_vpc.prod-vpc.id
cidr_block = var.subnet_prefix[0].cidr_block
availability_zone = "us-east-1a"
tags = {
Name = var.subnet_prefix[0].name
}
}
Creates the first subnet with the first item in the list (
[0]
).Dynamically assigns CIDR and name from the
subnet_prefix
variable.
hCopyEditresource "aws_subnet" "subnet-2" {
vpc_id = aws_vpc.prod-vpc.id
cidr_block = var.subnet_prefix[1].cidr_block
availability_zone = "us-east-1a"
tags = {
Name = var.subnet_prefix[1].name
}
}
- Similar to
subnet-1
, but uses the second object in the list ([1]
).
terraform.tfvars
– Defining Variable Values
hCopyEditsubnet_prefix = [
{
cidr_block = "10.0.1.0/24"
name = "prod-subnet"
},
{
cidr_block = "10.0.2.0/24"
name = "dev-subnet"
}
]
This is a list of maps, each with:
cidr_block
: the IP range for the subnet.name
: subnet tag.
Automatically picked up when you run
terraform apply
if the filename isterraform.tfvars
.
How to Run
bashCopyEditterraform init # Initialize the working directory
terraform plan # See the execution plan with variables applied
terraform apply # Apply changes using values from terraform.tfvars
Or explicitly:
bashCopyEditterraform apply -var-file="terraform.tfvars"
Benefits of Using Variables
Feature | Benefit |
Reusability | Change CIDRs/subnet names without editing code |
Cleaner Code | Avoid clutter by abstracting values |
Scalability | Easily manage multiple subnets, VPCs, etc. |
Config Management | Keep sensitive or environment-specific data separate |
Bonus Tip – Variable Type Safety
You can (and should) define a type for clarity and validation:
hCopyEditvariable "subnet_prefix" {
type = list(object({
cidr_block = string
name = string
}))
description = "List of subnets with CIDR and name"
}
This helps Terraform validate the input structure before execution.
Subscribe to my newsletter
Read articles from Arijit Das directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
