Infrastructure as Code with Terraform

Deploy a full AWS VPC and web server using Terraform code.

Introduction

30 Second Summary

You already know how to build a VPC in the AWS Console. But if you need that same setup in another region or account, you have to start over and click through every screen again.

In this project, you will write a single Terraform configuration file that creates a complete AWS network and web server. You will deploy it, modify it, and tear it all down from the command line.

What You'll Build

You will type a single command and watch your entire AWS environment appear, then change one security group rule and see Terraform update only that piece without rebuilding anything else.

By the end of this project, you'll have:

  • A VPC with a public subnet, internet gateway, and routing, all created from a single configuration file.
  • An EC2 instance running a web page you can visit in your browser.
  • A live demonstration of Terraform's differential updates: change one security group rule and watch Terraform modify only that resource.
  • Secret Mission: Make your configuration reusable by adding Terraform variables and outputs.

Are there any prerequisites?

You'll need an AWS account with free tier access. This project assumes you're familiar with VPC concepts like subnets, security groups, and internet gateways from prior experience or the NextWork VPC project series.

Before We Start

Before we dive in, let's lock in what you're about to build and why it matters for your career as a cloud engineer.

Install Terraform and AWS CLI

You cannot write Infrastructure as Code without the tools to run it. Terraform translates your configuration files into API calls that create real cloud resources. The AWS CLI connects your machine to your AWS account so Terraform can authenticate when it talks to AWS.

In this step, you will install both tools on Windows and verify that your machine can reach your AWS account from the command line.

In this step, get ready to:
  • Install Terraform v1.15.8 on Windows.
  • Install AWS CLI v2 on Windows.
  • Configure and verify your AWS credentials.
Download and install Terraform

Terraform is distributed as a single executable file. You will download it, place it in a folder, and tell Windows where to find it.

  • Press the Windows key to open your search bar.
  • Type PowerShell and press Enter to open PowerShell.
  • Create a folder for the Terraform binary by running this command:
mkdir C:\terraform

What does this command do?

This creates a new folder at C:\terraform. You will place the Terraform executable inside this folder in the next few steps.

PowerShell prints the details of the new directory, confirming it was created.

  • Open your browser and navigate to the Terraform install page.
  • Scroll down to the Windows section.
  • Under Binary download, click the AMD64 link next to Version: 1.15.8 to download the zip file.
  • Open the downloaded zip file in File Explorer and copy terraform.exe into the C:\terraform folder you just created.
  • Confirm that terraform.exe is in the folder by running this command in PowerShell:
ls C:\terraform

You should see terraform.exe listed in the output.

Windows needs to know where terraform.exe lives so you can run it from any folder. You do this by adding C:\terraform to the system PATH.

  • Press the Windows key, type environment variables, and press Enter.
  • In the System Properties dialog, click Environment Variables.
  • Under System variables, select Path and click Edit.
  • Click New and type C:\terraform.
  • Click OK to close each dialog.

Why do I need to open a new PowerShell window?

Existing PowerShell windows do not pick up PATH changes automatically. You must close your current window and open a fresh one for the change to take effect.

  • Close your PowerShell window.
  • Press the Windows key, type PowerShell, and press Enter to open a new window.
  • Verify Terraform is installed by running:
terraform version

✔️ I see Terraform v1.15.8

Terraform is installed and ready to go. You should see Terraform v1.15.8 followed by your platform (windows_amd64). Continue to the next substep.

ⓧ I see an older version

You likely have a previous Terraform installation on your machine. The older version is taking priority in your PATH.

  • Open the PATH editor again (search for environment variables).
  • Move the C:\terraform entry above any other folder that contains an older terraform.exe.
  • Click OK, close PowerShell, open a new one, and run terraform version again.

ⓧ Command not found

Windows cannot find terraform.exe. Double-check these common causes:

  • Confirm terraform.exe exists inside C:\terraform (not inside a nested subfolder from the zip extraction).
  • Confirm you added C:\terraform to the System variables Path, not the User variables version.
  • Make sure you opened a new PowerShell window after editing the PATH.
Install AWS CLI v2

The AWS CLI lets you interact with AWS services from the command line. Terraform uses the credentials you configure in the AWS CLI to authenticate with your AWS account.

  • In your browser, navigate to the AWS CLI MSI installer URL:

https://awscli.amazonaws.com/AWSCLIV2.msi

  • The download starts automatically. Once complete, double-click the downloaded AWSCLIV2.msi file to launch the installer.

What will the installer look like?

The MSI installer walks you through a standard Windows setup wizard. You will see a license agreement, a destination folder selection, and a progress bar. Accept the defaults and click Next through each screen until the installation completes.

  • Follow the installer prompts and click Next to accept the default settings.
  • Click Install when prompted. If Windows asks for administrator permission, click Yes.
  • Click Finish to close the installer.
  • Close your current PowerShell window and open a new one (press the Windows key, type PowerShell, press Enter).
  • Verify the AWS CLI is installed by running:
aws --version

✔️ I see aws-cli/2.x

The AWS CLI is installed. You should see output starting with aws-cli/2. followed by your platform details. Continue to the next substep.

ⓧ I see aws-cli/1.x

You have AWS CLI version 1 installed. Version 1 and version 2 both use the aws command, so version 1 is taking priority.

  • Uninstall AWS CLI version 1 from Settings > Apps > Installed apps.
  • Download and run the AWSCLIV2.msi installer again from the link above.
  • Close PowerShell, open a new window, and run aws --version again.

ⓧ Command not found

Windows cannot find the aws command. Try these fixes:

  • Make sure you opened a new PowerShell window after the installer finished.
  • Re-run the AWSCLIV2.msi installer. The MSI automatically adds the CLI to your PATH.

Still having trouble with either install?

If neither tool is working after following the steps above, ask for help troubleshooting your Terraform or AWS CLI installation on Windows.

Configure AWS credentials

With both tools installed, you need to give the AWS CLI your IAM credentials so it can authenticate with your AWS account. Terraform will use these same credentials when it creates resources.

Where do I find my access keys?

You need an IAM Access Key ID and Secret Access Key. If you do not have these yet, sign in to the AWS Console, navigate to IAM > Users > your username > Security credentials > Create access key.

Copy the Access Key ID and Secret Access Key and store them somewhere safe. You will not be able to see the secret key again after closing the creation dialog.

The aws configure command is interactive. It will prompt you for four values, one at a time.

  • Run the credential configuration command:
aws configure
  • When prompted for AWS Access Key ID, paste your IAM Access Key ID and press Enter.
  • When prompted for AWS Secret Access Key, paste your IAM Secret Access Key and press Enter.
  • When prompted for Default region name, type us-east-1 and press Enter.
  • When prompted for Default output format, type json and press Enter.

What did aws configure just do?

It saved your credentials and preferences to a file on your machine. When you run aws or terraform commands that talk to AWS, they read from this file automatically. You will not need to paste your keys again.

Before you move on, what do you think will happen if your credentials are correct? And what might you see if something is wrong?

  • Verify your credentials are working by running:
aws sts get-caller-identity

You should see JSON output containing your AWS account ID, your user ARN, and your user ID. It will look similar to this:

{
    "UserId": "AIDASAMPLEUSERID",
    "Account": "123456789012",
    "Arn": "arn:aws:iam::123456789012:user/YourUserName"
}

If you see your account number in the Account field, your credentials are configured correctly.

Seeing an error instead of JSON?

If you see a message about invalid credentials or an inability to locate credentials, double-check that you pasted the correct Access Key ID and Secret Access Key. Run aws configure again to re-enter them.

If you are still stuck, help me troubleshoot my AWS CLI credentials configuration.

Your machine is now fully set up with Terraform and the AWS CLI. Next up, you will write your first Terraform configuration and watch it create a VPC in your AWS account.

Create Your First VPC with Terraform

Your tools are installed and your AWS credentials are ready to go. Now it is time to put them to work.

You already know how to create a VPC through the AWS Console by clicking through menus and filling in forms. In this step, you will describe that same VPC in a text file and watch Terraform build it for you.

In this step, get ready to:
  • Write a Terraform configuration file that defines an AWS VPC.
  • Run the Terraform init, plan, and apply workflow to create the VPC.
  • Verify the VPC exists in the AWS Console.
Write your Terraform configuration
  • Create your project folder and navigate into it by running these commands in PowerShell:
mkdir C:\terraform-aws-project
cd C:\terraform-aws-project
  • Confirm the folder exists by running:
ls

You should see the empty terraform-aws-project folder listed.

  • Press the Windows key to open your search bar.
  • Type Visual Studio Code and press Enter to open it.
  • Click File in the top menu bar, then click Open Folder.
  • Navigate to C:\terraform-aws-project and click Select Folder.
  • In the Explorer sidebar on the left, hover over the folder name and click the New File icon.
  • Type main.tf and press Enter.

What is a .tf file?

Terraform reads files ending in .tf and treats them as configuration written in HCL (HashiCorp Configuration Language). HCL looks similar to JSON but is designed to be readable and editable by humans.

Your main.tf file will contain three sections: the Terraform settings, the AWS provider configuration, and the VPC resource.

  • Add the Terraform settings and AWS provider configuration to main.tf by copying this code:
# Tell Terraform to use the AWS provider
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 6.0"
    }
  }

  required_version = ">= 1.2"
}

# Set the region for all AWS resources
provider "aws" {
  region = "us-east-1"
}

What does this code do?

  • The terraform block tells Terraform which provider to download. Here you are requesting the official AWS provider from HashiCorp, pinned to any version in the 6.x range.
  • The required_version line ensures anyone running this file has Terraform 1.2 or higher installed.
  • The provider "aws" block sets the default region. Every AWS resource you create will land in us-east-1 unless you override it.
  • Save main.tf by pressing Ctrl+S.
  • Switch back to your PowerShell window (it should still be in C:\terraform-aws-project).
  • Download the AWS provider by running:
terraform init

You should see Terraform download the AWS provider and print Terraform has been successfully initialized! at the end of the output.

Seeing an error during init?

  • If you see a message about credentials, make sure aws configure was completed in Step 1 and your access key is valid.
  • If PowerShell cannot find terraform, close PowerShell and open a new window. The PATH change from Step 1 only takes effect in new sessions.
  • If you see a provider version error, double-check that the version = "~> 6.0" line in your main.tf matches exactly.

Help me troubleshoot a terraform init error.

Now that Terraform knows which provider to use, you can add the VPC resource.

  • Switch back to VS Code and add the following code below the provider block in main.tf:
# Create a VPC with DNS support enabled
resource "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_support   = true
  enable_dns_hostnames = true

  tags = {
    Name = "terraform-vpc"
  }
}

What does this resource block do?

  • The resource keyword tells Terraform to create something in AWS. The type aws_vpc means a Virtual Private Cloud, and "main" is the name you give it inside your Terraform code.
  • The cidr_block sets the IP address range for the VPC. 10.0.0.0/16 gives you over 65,000 available addresses.
  • The enable_dns_support and enable_dns_hostnames settings let resources inside the VPC resolve domain names. You will need this when the EC2 instance installs packages later.
  • The tags block adds a human-readable name (terraform-vpc) so you can identify this VPC in the AWS Console.
  • Save main.tf by pressing Ctrl+S.

✔️ Awesome, I've got everything!

Great. Double check you have saved main.tf and move on to deploy your VPC.

ⓧ I'd like to double check the full code

Here is the complete main.tf file. Compare it with yours and fix any differences:

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 6.0"
    }
  }

  required_version = ">= 1.2"
}

provider "aws" {
  region = "us-east-1"
}

resource "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_support   = true
  enable_dns_hostnames = true

  tags = {
    Name = "terraform-vpc"
  }
}
Deploy your VPC

Your configuration file is complete. Terraform follows a two-step safety process before it touches anything in AWS: first it shows you a plan, then it asks for your confirmation.

  • Switch back to your PowerShell window and preview what Terraform will create by running:
terraform plan

You should see Terraform describe the VPC it plans to create, followed by a summary line: Plan: 1 to add, 0 to change, 0 to destroy.

What does terraform plan do?

The plan command compares your .tf files against the current state of your AWS account. It shows you exactly what Terraform will create, modify, or destroy before it does anything. Think of it as a dry run.

The + symbol next to each attribute means Terraform will create that value. You will see different symbols in later steps when Terraform modifies or removes resources.

Before you run the next command, think about what you expect to happen. How many resources should Terraform create?

  • Create the VPC by running:
terraform apply

What should I see?

Terraform will show the same plan again and then ask: Do you want to perform these actions? Type yes and press Enter to confirm.

After a few seconds, you should see Apply complete! Resources: 1 added, 0 changed, 0 destroyed. at the bottom of the output. Your VPC now exists in AWS.

What is terraform.tfstate?

After applying, Terraform creates a file called terraform.tfstate in your project folder. This file records every resource Terraform created and their current settings.

Terraform reads this file on every plan and apply to figure out what has changed. Never edit this file by hand.

  • Open your browser and go to the AWS Console at https://console.aws.amazon.com.
  • Type VPC in the search bar at the top and click VPC to open the VPC dashboard.
  • Click Your VPCs in the left sidebar.
  • Look for the VPC named terraform-vpc with a CIDR of 10.0.0.0/16. This is the VPC Terraform just created from your code.

Don't see the VPC?

  • Make sure you are viewing the us-east-1 region. Check the region selector in the top-right corner of the AWS Console.
  • Go back to your PowerShell output and confirm you saw the Apply complete! message. If the apply failed, re-run terraform apply and check for error messages about credentials or permissions.

Help me find the VPC that Terraform created in the AWS Console.

Your first VPC is live, built entirely from code. In the next step, you will add the networking layer that makes this VPC functional: a public subnet, internet gateway, and route table.

Build the Network Layer

In Step 2, you wrote a main.tf file and watched Terraform create a VPC from code. That VPC exists in AWS, but right now it is an empty shell. Nothing can run inside it, and nothing can reach the internet.

To make the VPC functional, you need three things: a subnet to carve out IP addresses, an internet gateway to connect to the outside world, and a route table to direct traffic. In this step, you will add all three to your configuration file and deploy them.

In this step, get ready to:
  • Add a public subnet to your VPC configuration.
  • Add an internet gateway, route table, and route table association.
  • Deploy all four new resources and confirm them in the AWS Console.
Add a public subnet

A subnet carves out a smaller IP range inside your VPC. Setting map_public_ip_on_launch to true means every instance launched in this subnet automatically gets a public IP address.

  • In VS Code, open main.tf in your C:\terraform-aws-project folder.
  • Add the following code below the closing } of the aws_vpc resource:
# Create a public subnet inside the VPC
resource "aws_subnet" "public" {
  vpc_id                  = aws_vpc.main.id
  cidr_block              = "10.0.1.0/24"
  map_public_ip_on_launch = true
  availability_zone       = "us-east-1a"

  tags = {
    Name = "terraform-public-subnet"
  }
}

What does this code do?

  • vpc_id = aws_vpc.main.id tells Terraform to place this subnet inside the VPC you already created. Terraform automatically resolves this reference.
  • cidr_block = "10.0.1.0/24" carves out 256 IP addresses from your VPC's larger 10.0.0.0/16 range.
  • availability_zone = "us-east-1a" pins the subnet to a specific data center within the us-east-1 region.
  • Save main.tf.
  • Preview what Terraform will create by running this command in your PowerShell terminal:
terraform plan

You should see Terraform planning to add 1 new resource. Your existing VPC does not appear in the changes because Terraform knows it already exists.

Seeing an error instead of a plan?

  • Check that you pasted the subnet code after the VPC resource's closing }, not inside it. Each resource block must be its own top-level block.
  • Make sure you saved main.tf before running terraform plan.

Help me debug my terraform plan error for the subnet resource.

Add the internet gateway and routing

A subnet with public IPs still cannot reach the internet on its own. You need an internet gateway attached to the VPC, a route table that directs outbound traffic to that gateway, and an association that links the route table to the subnet.

  • Add the following code below the subnet resource you just added in main.tf:
# Internet gateway - connects the VPC to the public internet
resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id

  tags = {
    Name = "terraform-igw"
  }
}

# Route table - sends all outbound traffic through the internet gateway
resource "aws_route_table" "public" {
  vpc_id = aws_vpc.main.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.main.id
  }

  tags = {
    Name = "terraform-public-rt"
  }
}

# Link the route table to the public subnet
resource "aws_route_table_association" "public" {
  subnet_id      = aws_subnet.public.id
  route_table_id = aws_route_table.public.id
}

How do these three resources connect?

Think of it as a chain. The internet gateway gives the VPC a door to the internet. The route table is a set of directions that says "send all traffic headed for 0.0.0.0/0 through that door." The route table association attaches those directions to your public subnet.

Terraform figures out the build order automatically. It reads aws_internet_gateway.main.id inside the route table and knows the gateway must be created first.

  • Save main.tf. You now have all four new resources in the file, ready to deploy in the next substep.
Deploy the network resources

Your main.tf now describes a VPC with a full networking layer. Before you apply, take a moment to predict what Terraform will do.

You added four new resources, but the VPC already exists. Do you think Terraform will rebuild the VPC, or leave it alone and only create the new resources?

  • Preview the changes by running:
terraform plan

You should see Plan: 4 to add, 0 to change, 0 to destroy. Terraform plans to create the subnet, internet gateway, route table, and route table association. The VPC is not listed because Terraform already tracks it in the state file and sees no changes to it.

Why does Terraform skip the VPC?

When you ran terraform apply in Step 2, Terraform saved the VPC's details in a local file called terraform.tfstate. On every subsequent plan, Terraform compares your code against that state file. If a resource already matches, it skips it. Only differences get created, updated, or destroyed.

  • Deploy the four new resources by running:
terraform apply
  • When prompted, type yes and press Enter to confirm.

You should see Apply complete! Resources: 4 added, 0 changed, 0 destroyed. at the end of the output.

  • Open the AWS Console in your browser and navigate to the VPC service.
  • Click Subnets in the left sidebar. You should see terraform-public-subnet with CIDR 10.0.1.0/24.
  • Click Internet gateways in the left sidebar. You should see terraform-igw attached to terraform-vpc.
  • Click Route tables in the left sidebar. You should see terraform-public-rt with a route pointing 0.0.0.0/0 at the internet gateway.

Resources missing from the console?

  • Make sure the region selector in the top-right corner of the AWS Console is set to US East (N. Virginia) us-east-1. Resources only appear in the region where they were created.
  • If terraform apply showed an error instead of the success message, check that each resource block in main.tf has matching opening and closing braces.

Help me figure out why my Terraform resources are not showing up in the AWS Console.

✔️ Awesome, I've got everything!

Great. Double check you have saved main.tf and that all four new resources appeared in the AWS Console.

ⓧ I'd like to double check the full code

Your main.tf should contain these blocks in order. Scroll up and confirm each one is present:

  • The terraform block with the AWS provider (hashicorp/aws, version ~> 6.0) and required_version = ">= 1.2".
  • The provider "aws" block with region = "us-east-1".
  • The aws_vpc "main" resource with CIDR 10.0.0.0/16 and Name tag terraform-vpc.
  • The aws_subnet "public" resource with CIDR 10.0.1.0/24 and Name tag terraform-public-subnet.
  • The aws_internet_gateway "main" resource with Name tag terraform-igw.
  • The aws_route_table "public" resource with a route sending 0.0.0.0/0 to the internet gateway and Name tag terraform-public-rt.
  • The aws_route_table_association "public" resource linking the subnet to the route table.

Your VPC now has a complete networking layer. Next up, you will deploy an EC2 instance running a web server into this network and see what happens when the security group blocks all inbound traffic.

Deploy a Web Server

In the last step, you built out the full networking layer for your VPC. Your public subnet, internet gateway, and route table are all live in AWS.

But right now, nothing is actually running inside that network. In this step, you'll deploy an EC2 instance with a web server into the public subnet. You'll also create a security group to control traffic flow.

In this step, get ready to:
  • Create a security group with outbound traffic rules.
  • Deploy an EC2 instance running an Apache web server.
  • Test the web server by visiting it in your browser.
Add the security group and egress rule

Before launching an EC2 instance, you need a security group to act as its firewall. You already know how security groups work from your VPC networking experience. This time, you'll define one in code instead of clicking through the console.

  • In main.tf, scroll to the bottom of the file (after the aws_route_table_association resource) and add the following code:
# Firewall rules for the web server
resource "aws_security_group" "web" {
  name        = "terraform-web-sg"
  description = "Security group for web server"
  vpc_id      = aws_vpc.main.id

  tags = {
    Name = "terraform-web-sg"
  }
}

# Allow all outbound traffic so the instance can download packages
resource "aws_vpc_security_group_egress_rule" "all_outbound" {
  security_group_id = aws_security_group.web.id
  cidr_ipv4         = "0.0.0.0/0"
  ip_protocol       = "-1"
}

What does this code do?

The aws_security_group resource creates a security group inside your VPC. It references aws_vpc.main.id to attach itself to the VPC you created in Step 2.

The aws_vpc_security_group_egress_rule resource is a separate resource that defines an outbound rule. Setting ip_protocol = "-1" means all protocols are allowed. This lets the instance reach the internet to download software.

Why are rules separate resources?

The AWS provider recommends defining ingress and egress rules as their own resources instead of inline blocks on the security group. This makes each rule independently manageable. Terraform can add, change, or remove a single rule without touching the security group itself.

  • Save main.tf.
  • Preview what Terraform will create by running this command in your PowerShell window:
terraform plan

You should see a plan summary ending with Plan: 2 to add, 0 to change, 0 to destroy. Terraform knows the VPC and networking resources already exist, so it only plans to create the two new resources.

Seeing an error instead of a plan?

  • Check for typos in resource names. aws_vpc_security_group_egress_rule is a long name. Make sure it matches exactly.
  • Make sure security_group_id references aws_security_group.web.id. A missing .id at the end will cause an error.
  • Confirm you saved the file before running the command.

Help me fix a Terraform plan error for my security group resources.

Add the AMI lookup and web server instance

Every EC2 instance needs an AMI (Amazon Machine Image) to boot from. Instead of hardcoding a specific AMI ID that could change over time, you'll use a data source to automatically find the latest one.

  • Add the AMI lookup below the egress rule in main.tf by copying the following code:
# Look up the latest Amazon Linux 2023 AMI automatically
data "aws_ami" "amazon_linux" {
  most_recent = true
  owners      = ["amazon"]

  # Match Amazon Linux 2023 x86_64 images
  filter {
    name   = "name"
    values = ["al2023-ami-2023.*-x86_64"]
  }

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

What is a data source?

A data block does not create anything. It reads information from AWS and makes it available to other resources. Here, it queries AWS for the most recent Amazon Linux 2023 AMI and stores the result so your EC2 instance can reference it.

  • Now add the EC2 instance and an output block below the AMI data source:
# Launch the web server in the public subnet
resource "aws_instance" "web" {
  ami                    = data.aws_ami.amazon_linux.id
  instance_type          = "t2.micro"
  subnet_id              = aws_subnet.public.id
  vpc_security_group_ids = [aws_security_group.web.id]

  # Install Apache and create a simple web page on boot
  user_data = <<-EOF
    #!/bin/bash
    dnf install -y httpd
    systemctl start httpd
    systemctl enable httpd
    echo "<h1>Hello from Terraform!</h1>" > /var/www/html/index.html
  EOF

  tags = {
    Name = "terraform-web-server"
  }
}

# Display the instance's public IP after apply
output "instance_public_ip" {
  description = "Public IP of the web server"
  value       = aws_instance.web.public_ip
}

What does this code do?

  • ami references the AMI that the data source just looked up, so your instance always uses the latest Amazon Linux 2023 image.
  • instance_type is set to t2.micro, which is covered by the AWS Free Tier.
  • user_data is a startup script that runs when the instance first launches. It installs Apache (httpd), starts the web server, and writes a simple HTML page.
  • The output block tells Terraform to print the instance's public IP address after the apply finishes, so you know where to find your web server.
  • Save main.tf.

Awesome, I've got everything!

Great. Double check you've saved main.tf and move on to deploying your web server.

I'd like to double check the full code

Your main.tf is now longer than 30 lines, so scroll through it and confirm the following blocks are present, in this order:

  • terraform block with required_providers (from Step 2).
  • provider "aws" block with region = "us-east-1" (from Step 2).
  • aws_vpc "main" resource (from Step 2).
  • aws_subnet "public", aws_internet_gateway "main", aws_route_table "public", and aws_route_table_association "public" (from Step 3).
  • aws_security_group "web" and aws_vpc_security_group_egress_rule "all_outbound" (added in this step).
  • data "aws_ami" "amazon_linux" block (added in this step).
  • aws_instance "web" resource and output "instance_public_ip" block (added in this step).
Deploy and test the web server

All the code is in place. Time to let Terraform build the security group, look up the AMI, and launch the instance.

  • Deploy everything by running this command:
terraform apply
  • Terraform will show you a plan first. Review it, then type yes and press Enter to confirm.

What should I see?

Terraform will take a minute or two to create the resources. When it finishes, you'll see a summary line: Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

Below the summary, you'll see the Outputs: section with your instance's public IP address. Copy that IP address. You'll need it in a moment.

Before you open this IP in your browser, think about the security group you just created. What traffic rules does it have? Do you expect the web page to load?

  • Open your browser and paste the public IP address into the address bar. Press Enter.

The page does not load. Your browser spins and eventually times out.

Why does the page time out?

This is working exactly as designed. Look back at the security group code. You added an egress rule (outbound traffic), but you did not add any ingress rule (inbound traffic).

Without an inbound rule allowing HTTP traffic on port 80, the security group blocks every incoming request. Your web server is running, but no one can reach it. This is the gap you'll close in the next step.

Your web server is live in AWS, but it is locked behind a security group with no inbound rule. In the next step, you'll add a single HTTP ingress rule and watch Terraform surgically update only that one resource, leaving everything else untouched.

Modify and Destroy Infrastructure

In the previous step, you deployed a web server with a public IP. When you tried to visit it in your browser, the page timed out.

That timeout happened because your security group has no inbound rule allowing HTTP traffic. Instead of tearing everything down and starting over, you will add a single ingress rule and watch Terraform surgically update only that one resource. Then you will destroy the entire environment with one command.

In this step, get ready to:
  • Add an HTTP ingress rule to the security group.
  • Watch Terraform apply only the change you made without rebuilding anything else.
  • Destroy all infrastructure with a single command.
Add an HTTP ingress rule

Your web server is running and has a public IP, but the security group blocks all inbound traffic. You need to open port 80 for HTTP requests.

  • In main.tf, find the closing } of the aws_security_group "web" resource block.
  • Add the following code between the security group block and the aws_vpc_security_group_egress_rule block:
resource "aws_vpc_security_group_ingress_rule" "http" {
  security_group_id = aws_security_group.web.id
  cidr_ipv4         = "0.0.0.0/0"
  from_port         = 80
  to_port           = 80
  ip_protocol       = "tcp"
  description       = "Allow HTTP"
}

What does this code do?

  • aws_vpc_security_group_ingress_rule creates a standalone inbound traffic rule for your security group. This is the current best practice instead of putting rules inline on the security group itself.
  • from_port = 80 and to_port = 80 open only port 80, the standard HTTP port.
  • cidr_ipv4 = "0.0.0.0/0" allows traffic from any IP address on the internet.
  • Save main.tf.

Seeing a syntax error?

  • Make sure the new resource block sits between the aws_security_group "web" closing brace and the aws_vpc_security_group_egress_rule block. It should not be nested inside another resource.
  • Check that every opening { has a matching closing }.

Still stuck? Ask for help.

✔️ Awesome, I've got everything!

Great. Double check you have saved main.tf and let's move on.

ⓧ I'd like to double check the full code

Your main.tf is now over 90 lines. Scroll through and confirm these blocks are present, in this order:

  • terraform block with the AWS provider source and version.
  • provider "aws" block with region = "us-east-1".
  • aws_vpc "main" resource.
  • aws_subnet "public" resource.
  • aws_internet_gateway "main" resource.
  • aws_route_table "public" resource with the 0.0.0.0/0 route.
  • aws_route_table_association "public" resource.
  • aws_security_group "web" resource.
  • aws_vpc_security_group_ingress_rule "http" resource (the block you just added).
  • aws_vpc_security_group_egress_rule "all_outbound" resource.
  • data "aws_ami" "amazon_linux" block.
  • aws_instance "web" resource with user_data.
  • output "instance_public_ip" block.
Preview and apply the change

You added one small block to your configuration. Before applying, Terraform can show you exactly what it plans to change.

How many resources do you think Terraform will add, change, or destroy? Will it rebuild the VPC or EC2 instance, or just add the new rule?

  • Preview the planned change by running:
terraform plan

At the bottom of the output, you should see Plan: 1 to add, 0 to change, 0 to destroy. Terraform read the state file, compared it to your updated configuration, and found one difference: the new ingress rule.

Why is this a big deal?

In a traditional console workflow, you would navigate to the security group, click edit, add the rule, and save. With Terraform, the change lives in your code file. It is reviewable before it runs, repeatable across environments, and tracked in version control.

The plan also proves that Terraform is not rebuilding your VPC, subnet, or EC2 instance. It only touches what changed.

  • Apply the change by running:
terraform apply
  • Type yes when prompted to confirm.

Terraform confirms Apply complete! Resources: 1 added, 0 changed, 0 destroyed. Your security group now allows HTTP traffic on port 80.

Do you think the web page will load now that the ingress rule is in place?

  • Switch to your browser and refresh the page at your instance's public IP.

You should see Hello from Terraform! displayed on the page. The security group now lets HTTP traffic through to your web server.

Page still not loading?

  • Make sure you are using http:// and not https:// in the URL. Your server only listens on port 80 (HTTP).
  • The EC2 instance may need a minute or two after launch for Apache to finish installing. Wait 30 seconds and refresh again.
  • Confirm the terraform apply output showed 1 added. If it showed 0 added, double check that you saved main.tf before running the command.

Still stuck? Ask for help.

Destroy all infrastructure with one command

Your infrastructure is working. For this learning project, you should not leave resources running in AWS. Terraform can remove every resource it created with a single command.

Before you run this, think about the order Terraform needs to follow. Can it delete the VPC before removing the subnet inside it? What about the EC2 instance that depends on the subnet and security group?

  • Destroy all resources by running:
terraform destroy
  • Terraform displays a plan showing every resource it will remove. Type yes when prompted to confirm.

Watch the output as Terraform removes resources in the correct dependency order. It deletes the EC2 instance and security group rules first, then the security group, route table association, internet gateway, subnet, and finally the VPC.

When it finishes, you should see Destroy complete! followed by the count of resources destroyed.

Why does dependency order matter?

Terraform tracks which resources depend on others. It cannot delete a VPC that still has subnets or an internet gateway attached.

By reading the dependency graph from your configuration, Terraform automatically removes child resources before their parents. You did not have to specify any ordering.

  • Switch to the AWS Console in your browser.
  • Navigate to VPC, then select Your VPCs.
  • Confirm that the terraform-vpc VPC is no longer listed.

Still see the VPC?

  • Make sure you are looking at the correct region (us-east-1) in the top right corner of the AWS Console.
  • Refresh the page. The console sometimes caches the previous state.
  • Check your PowerShell output to confirm terraform destroy finished with Destroy complete! and not an error.

Need help debugging? Ask here.

Secret mission

Add Variables and Outputs

Your Terraform configuration works, but every value is hardcoded. What if you want to deploy the same infrastructure in a different AWS region or change the instance type? Right now, you would have to edit main.tf every time. This mission makes your configuration reusable.

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. If you followed Step 5 or the Secret Mission, you already ran terraform destroy and your AWS infrastructure is already gone.

Cost warning

If you re-deployed infrastructure after Step 5 and did not run terraform destroy, your EC2 instance is still running. The t2.micro instance is free for 750 hours/month in the first 12 months of your AWS account. After the free tier expires, it costs approximately $0.0116/hour (around $8.50/month).

The other resources (VPC, subnet, internet gateway, route table, security group) have no hourly cost.

Resources you used:

  • AWS VPC, public subnet, internet gateway, route table, and route table association.
  • AWS security group and security group rules (ingress and egress).
  • AWS EC2 instance (t2.micro) running a web server.
  • Local project folder C:\terraform-aws-project containing main.tf, variables.tf, outputs.tf, and Terraform state files.
  • Terraform binary installed at C:\terraform (added to system PATH).
  • AWS CLI v2 installed via MSI.
  • AWS credentials stored in %UserProfile%\.aws.

Keep everything running

No action needed. Choose this if you plan to keep building with Terraform and AWS.

  • Your Terraform files in C:\terraform-aws-project are ready for future projects.
  • If you already ran terraform destroy, no AWS resources are running and there are no ongoing costs.
  • Terraform and AWS CLI remain installed on your machine for future use.
  • Your AWS credentials in %UserProfile%\.aws remain configured for future AWS CLI and Terraform work.

Pause - I'll come back to this later

Destroy any running AWS infrastructure to stop charges, but keep your local files and tools installed.

  • If you have not already run terraform destroy, open PowerShell, navigate to C:\terraform-aws-project, and run the following command:
  • Type yes when prompted to confirm.
terraform destroy
  • If you already ran terraform destroy in Step 5 or the Secret Mission, no further action is needed. Your AWS resources are already gone.
  • Keep C:\terraform-aws-project on your machine so you can re-deploy later with terraform apply.

Delete - I don't want to use this again

Remove all project resources, tools, and configuration from your machine.

Destroy AWS infrastructure:

  • If you have not already run terraform destroy, open PowerShell, navigate to C:\terraform-aws-project, and run:
terraform destroy
  • Type yes when prompted to confirm.
  • If you already ran terraform destroy in Step 5 or the Secret Mission, skip this. Your AWS resources are already gone.

Delete the project folder:

  • Delete C:\terraform-aws-project by running this in PowerShell:
Remove-Item -Recurse -Force C:\terraform-aws-project

Remove the Terraform binary:

  • Delete the C:\terraform folder by running:
Remove-Item -Recurse -Force C:\terraform
  • Remove C:\terraform from your system PATH. Open System Properties, click Environment Variables, select Path under System variables, click Edit, and delete the C:\terraform entry.

Uninstall AWS CLI v2:

  • Open Control Panel, then select Programs and Features.
  • Select AWS Command Line Interface and click Uninstall.
  • Confirm the uninstallation when prompted.

Remove AWS credentials:

  • Delete the .aws folder in your user profile by running:
Remove-Item -Recurse -Force $env:USERPROFILE\.aws

Removing AWS credentials affects other tools

The .aws folder stores credentials and configuration shared by all AWS SDKs and the AWS CLI. If you use other AWS tools or SDKs on this machine, skip deleting this folder.

That's a Wrap!

That's a Wrap!

Nice work! You just built, modified, and destroyed a complete AWS environment entirely from code using Terraform.

You've learned how to:

  • Write a Terraform configuration from scratch that creates a VPC, public subnet, internet gateway, route table, security group, and EC2 web server.
  • Use the full Terraform workflow: terraform init to download providers, terraform plan to preview changes, terraform apply to create infrastructure, and terraform destroy to tear it all down.
  • See differential updates in action by adding an HTTP ingress rule and watching Terraform modify only that one resource while leaving everything else untouched.
  • Secret Mission: Refactored the configuration with variables and outputs, making it portable across AWS regions and reusable for different environments.

Ready to quiz yourself?