Build a Web Server: Console to Terraform

Build a VPC and web server by hand, then recreate it with Terraform.

Introduction

30 Second Summary

Setting up a server by clicking through a cloud console works fine the first time. But when you need to rebuild it, duplicate it across environments, or hand it off to a teammate, those clicks become a liability.

In this project, you will build a live web server on AWS twice: once by hand in the console to understand every networking component, and once with Terraform so you can recreate the entire thing in under a minute with a single command.

What You'll Build

You will run terraform apply and watch an entire networking stack plus web server appear on AWS in about 60 seconds, then open the URL in your browser to see a live page served from infrastructure that exists entirely as code.

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

  • A live web page you can open in any browser, served by a server running inside a custom VPC network you designed from scratch.
  • A single command (terraform apply) that rebuilds your entire infrastructure in under 60 seconds, proving it lives as code you can version-control and share.
  • A one-command teardown (terraform destroy) that cleanly removes every resource, leaving zero surprise charges on your account.
  • Secret Mission: Add a second web server in a different availability zone and watch Terraform scale your infrastructure with just a few extra lines of code.

Are there any prerequisites?

You need an AWS account with an IAM user configured for programmatic access (an Access Key ID and Secret Access Key). If you do not have one yet, create a free AWS account and set up an IAM user before starting Step 1.

Before We Start

Before we dive in, let's lock in what you're about to build and why it matters.

You're going to build the same infrastructure twice on purpose. First by hand in the AWS Console, then by writing code with Terraform. That deliberate repetition is how you'll internalize why Infrastructure as Code exists.

Set Up Your Tools

To build and automate cloud infrastructure, you need two command-line tools on your machine. The AWS CLI lets you talk to your AWS account from the terminal. Terraform lets you define infrastructure as code and deploy it with a single command.

In this step, you'll install both tools and connect them to your AWS account. Once that's done, your machine will be ready for the hands-on cloud work ahead.

In this step, get ready to:
  • Install the AWS CLI.
  • Install Terraform.
  • Configure your AWS credentials.
Install the AWS CLI

The AWS CLI is the tool that lets your terminal send commands to AWS. You'll use it to authenticate and verify your connection before building any infrastructure.

  • Press the Windows key to open your search bar.
  • Type PowerShell and press Enter to open it.
  • Check whether the AWS CLI is already installed by running this command:
aws --version

✔️ I see version 2.x or higher

Your AWS CLI is ready to go. Skip ahead to the Terraform install below.

ⓧ I see version 1.x

AWS CLI version 1 is outdated and missing features this project needs. You need to upgrade to version 2.

  • Download the AWS CLI v2 installer from this link:
  • https://awscli.amazonaws.com/AWSCLIV2.msi
  • Run the downloaded .msi file and follow the installer prompts.
  • Close your current PowerShell window after the install completes.
  • Open a new PowerShell window (press the Windows key, type PowerShell, press Enter).
  • Run aws --version again to confirm you see version 2.x.

ⓧ Command not found

The AWS CLI is not installed yet. Follow these steps to install it.

  • Download the AWS CLI v2 installer from this link:
  • https://awscli.amazonaws.com/AWSCLIV2.msi
  • Run the downloaded .msi file and follow the installer prompts (accept the defaults).
  • Close your current PowerShell window after the install completes.
  • Open a new PowerShell window (press the Windows key, type PowerShell, press Enter).
  • Confirm the install by running:
aws --version

You should see a version number starting with aws-cli/2.

Still seeing 'command not found'?

Make sure you opened a brand-new PowerShell window after the install finished. The old window does not pick up the new PATH entries.

If it still fails, try restarting your computer and opening PowerShell again. Help me troubleshoot my AWS CLI installation.

Install Terraform

Terraform is the tool that turns your infrastructure configuration files into real cloud resources. This project requires Terraform version 1.5 or higher.

  • Check whether Terraform is already installed by running:
terraform version

✔️ I see version 1.5 or higher

Your Terraform is ready. Skip ahead to the credential configuration below.

ⓧ I see an older version

You have Terraform installed, but the version is too old for this project. Download the latest version to replace it.

  • Go to the Terraform install page:
  • https://developer.hashicorp.com/terraform/install
  • Download the Windows AMD64 binary (version 1.15.8).
  • Extract terraform.exe from the zip file.
  • Replace the old terraform.exe wherever it is currently installed with this new file.
  • Close and reopen PowerShell.
  • Run terraform version to confirm you see 1.5 or higher.

ⓧ Command not found

Terraform is not installed yet. Follow these steps to install it.

  • Go to the Terraform install page:
  • https://developer.hashicorp.com/terraform/install
  • Download the Windows AMD64 binary (version 1.15.8).
  • Create a folder called C:\terraform on your computer.
  • Extract terraform.exe from the zip file into C:\terraform.

Now add that folder to your system PATH so PowerShell can find it.

  • Press the Windows key and search for Edit the system environment variables.
  • Press Enter to open the System Properties window.
  • Click Environment Variables at the bottom.
  • Under System variables, select the Path row and click Edit.
  • Click New and type C:\terraform.
  • Click OK on all open windows to save.
  • Close and reopen PowerShell.
  • Confirm the install by running:
terraform version

You should see Terraform v1.15.8 (or whichever version you downloaded).

Terraform command still not found?

Double-check that C:\terraform contains terraform.exe (not a nested subfolder with the file inside it).

Make sure you opened a brand-new PowerShell window after editing the PATH. The old window will not see the change. Help me get Terraform working in PowerShell.

Configure your AWS credentials

Both tools are installed. Now you need to tell the AWS CLI who you are so it can make requests on your behalf.

  • Run this command in PowerShell to start the configuration wizard:
aws configure

What does aws configure ask for?

The command prompts you for four values, one at a time. You'll enter each value and press Enter to move to the next.

  • When prompted for AWS Access Key ID, paste your IAM user's Access Key ID and press Enter.
  • When prompted for AWS Secret Access Key, paste your 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.

Where do I find my Access Key ID and Secret Access Key?

These come from the IAM user you created when setting up your AWS account. If you downloaded a .csv file when creating the user, your keys are in that file.

If you lost them, you can create a new access key in the AWS Console under IAM > Users > your user > Security credentials tab.

Before you run the next command, what do you think it will return if your credentials are correct?

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

You should see a JSON response containing your Account ID, UserId, and Arn. This confirms your terminal is authenticated with AWS.

Seeing 'Unable to locate credentials'?

This means aws configure did not save your keys correctly. Run aws configure again and make sure you paste the full key values without extra spaces.

If you see 'InvalidClientTokenId', your Access Key ID is incorrect or the IAM user was deleted. Help me fix my AWS credential configuration.

Your tools are installed and your terminal can talk to AWS. Next up, you'll build a full network inside AWS by hand to understand every piece of the puzzle before automating it.

Build Your Network in the AWS Console

Your local tools are installed and authenticated. Now it's time to build real cloud infrastructure in AWS.

Before Terraform can automate anything, you need to understand what it is automating. Building the network by hand teaches you how VPCs, subnets, internet gateways, and route tables connect to give a server internet access.

In this step, get ready to:
  • Create a VPC and public subnet that defines your server's private network.
  • Attach an internet gateway so traffic can flow in and out.
  • Wire up a route table that directs outbound traffic through the gateway.
Create a VPC and public subnet

A VPC (Virtual Private Cloud) is an isolated virtual network inside AWS where your resources live. You will create one now, then add a subnet inside it to define the specific IP range your server will use.

  • Open your browser and navigate to https://console.aws.amazon.com.
  • Sign in with your IAM user credentials.
  • Confirm the region in the top-right corner is set to US East (N. Virginia) us-east-1.
  • Type VPC in the search bar at the top and select VPC from the results.
  • In the left sidebar, click Your VPCs.
  • Click Create VPC.
  • Select VPC only.
  • In the Name tag field, enter devops-vpc.
  • In the IPv4 CIDR field, enter 10.0.0.0/16.
  • Leave all other settings as default and click Create VPC.

You should see a green success banner confirming your VPC was created.

What is CIDR notation?

CIDR stands for Classless Inter-Domain Routing. The /16 in 10.0.0.0/16 means the first 16 bits are fixed (10.0), leaving the rest for your use. That gives you over 65,000 possible IP addresses inside this VPC.

Next, create a subnet inside your VPC to carve out a smaller IP range for your server.

  • Click Subnets in the left sidebar.
  • Click Create subnet.
  • In the VPC ID dropdown, select your devops-vpc.
  • In the Subnet name field, enter devops-public-subnet.
  • In the Availability Zone dropdown, select us-east-1a.
  • In the IPv4 subnet CIDR block field, enter 10.0.1.0/24.
  • Click Create subnet.

You should see your new devops-public-subnet listed in the subnets table.

One more setting to enable on this subnet.

  • Select devops-public-subnet in the subnets list by clicking its checkbox.
  • Click the Actions dropdown.
  • Select Edit subnet settings.
  • Under Auto-assign IP settings, check the box for Enable auto-assign public IPv4 address.
  • Click Save.

The subnet settings page now shows auto-assign public IPv4 as enabled.

Why enable auto-assign public IPv4?

Any server launched into this subnet will automatically receive a public IP address. Without this setting, your web server would only have a private IP and nobody on the internet could reach it.

Don't see your VPC or subnet?

Check that the region dropdown in the top-right corner shows US East (N. Virginia). Resources only appear in the region where they were created. If you accidentally created them in a different region, delete them there and recreate in us-east-1.

Help me find my VPC and subnet in the AWS Console.

Create and attach an internet gateway

Your VPC is isolated from the internet by default. An internet gateway is the bridge that allows traffic to flow between your VPC and the outside world.

  • Click Internet gateways in the left sidebar.
  • Click Create internet gateway.
  • In the Name tag field, enter devops-igw.
  • Click Create internet gateway.

The gateway is created, but it is not attached to any VPC yet. The green banner at the top includes a prompt to attach it.

  • Click the Actions dropdown.
  • Select Attach to VPC.
  • In the Available VPCs dropdown, select devops-vpc.
  • Click Attach internet gateway.

The state column for devops-igw now shows Attached with your VPC ID next to it.

What does attaching an internet gateway do?

Think of the internet gateway as the front door to your VPC. Creating it builds the door. Attaching it installs the door into your VPC's wall. Traffic still cannot flow through it until you add a route telling your subnet to use it.

Create a route table and associate your subnet

A route table contains rules that determine where network traffic goes. You need one that says "send all internet-bound traffic through the internet gateway."

  • Click Route tables in the left sidebar.
  • Click Create route table.
  • In the Name field, enter devops-public-rt.
  • In the VPC dropdown, select devops-vpc.
  • Click Create route table.

Your route table exists, but it only has the default local route. You need to add the internet route.

  • With devops-public-rt selected, click the Routes tab at the bottom of the page.
  • Click Edit routes.
  • Click Add route.
  • In the Destination field, enter 0.0.0.0/0.
  • In the Target dropdown, select Internet Gateway, then select devops-igw.
  • Click Save changes.

The Routes tab now shows two entries: the local route and your new 0.0.0.0/0 route pointing at the internet gateway.

What does 0.0.0.0/0 mean?

This is a catch-all route. It means "any IP address that doesn't match another route in this table." In practice, it sends all internet-bound traffic to your internet gateway.

Finally, associate your subnet with this route table so it actually uses these rules.

  • Click the Subnet associations tab.
  • Click Edit subnet associations.
  • Check the box next to devops-public-subnet.
  • Click Save associations.

Before you check the result, what do you expect to see in the route table's Routes tab and Subnet associations tab?

  • Click back to the Routes tab to confirm you see 0.0.0.0/0 pointing at your internet gateway.
  • Click the Subnet associations tab to confirm devops-public-subnet is listed under Explicit subnet associations.

Both should be present. Your network is fully wired: traffic from the subnet hits the route table, the route table sends internet-bound traffic to the gateway, and the gateway delivers it to the internet.

Route table missing the internet route?

If you only see the local 10.0.0.0/16 route, go back to Edit routes and add the 0.0.0.0/0 route again. Make sure you select Internet Gateway as the target type before picking devops-igw.

Help me fix my route table configuration.

Your virtual network is fully built. You have a VPC, a subnet with public IPs, an internet gateway, and a route table connecting them all together. Next up, you will launch a web server into this network and see a live page in your browser.

Launch Your Web Server and See It Live

You have a complete network in place. Your VPC, subnet, internet gateway, and route table are all wired together.

But a network with nothing on it is just empty plumbing. In this step, you will launch an EC2 instance that actually uses this network to serve a live web page. A security group will act as a firewall controlling what traffic reaches your server, and a user data script will install a web server automatically when the instance boots.

In this step, get ready to:
  • Create a security group that allows HTTP and SSH traffic into your server.
  • Launch an EC2 instance with a user data script that installs Apache.
  • Visit your web server's public IP in a browser and see your page live.
Create a security group

Before launching a server, you need to define what traffic is allowed to reach it. A security group is a virtual firewall that sits in front of your EC2 instance and only permits traffic you explicitly allow.

  • In the AWS Console, type EC2 in the top search bar and select the EC2 service.
  • In the left navigation pane, click Security Groups.
  • Click Create security group.
  • For Security group name, enter devops-web-sg.
  • For Description, enter Allow HTTP and SSH.
  • For VPC, remove the default VPC and select devops-vpc.

Now add the inbound rules that define what traffic can reach your server.

  • Under Inbound rules, click Add rule.
  • Set Type to HTTP.
  • Set Source to Anywhere-IPv4 (0.0.0.0/0).
  • Click Add rule again to add a second rule.
  • Set Type to SSH.
  • Set Source to Anywhere-IPv4 (0.0.0.0/0).
  • Leave the Outbound rules section as-is.
  • Click Create security group.

You should see a green banner confirming that devops-web-sg was created successfully.

What is a security group?

A security group is a stateful firewall for your EC2 instances. "Stateful" means that if you allow traffic in, the response is automatically allowed out. You only need to define the inbound rules.

Port 80 is the standard port for HTTP web traffic. Port 22 is for SSH, which lets you remotely connect to the server's terminal. The default outbound rule allows all traffic out, so your server can reach the internet to download packages.

Don't see devops-vpc in the VPC dropdown?

Make sure you are in the us-east-1 region. Check the region selector in the top-right corner of the console. Your VPC only exists in the region where you created it.

Help me find my VPC

Launch an EC2 instance

With the firewall rules in place, you can now launch a server that will be protected by them.

  • In the left navigation pane, click Instances.
  • Click Launch instances.
  • Under Name and tags, enter devops-web-server for the Name.
  • Under Application and OS Images (Amazon Machine Image), click Quick Start and select Amazon Linux.
  • Make sure Amazon Linux 2023 AMI is chosen in the AMI dropdown.
  • Under Instance type, select t3.micro.
  • Under Key pair (login), select Proceed without a key pair (Not recommended).

Now configure the networking to place your instance inside the VPC you built in the previous step.

  • Under Network settings, click Edit.
  • For VPC, select devops-vpc.
  • For Subnet, select devops-public-subnet.
  • For Firewall (security groups), choose Select existing security group.
  • Under Common security groups, select devops-web-sg.

Finally, add the user data script that will install a web server when the instance boots.

  • Scroll down and expand Advanced details.
  • Scroll to the bottom of the expanded section and find the User data text box.
  • Paste the following script into the User data field:
#!/bin/bash
yum update -y
yum install -y httpd
systemctl start httpd
systemctl enable httpd
echo "<h1>Hello from the Console!</h1><p>This web server is running on your custom VPC.</p>" > /var/www/html/index.html

What does this script do?

  • yum update -y updates all system packages on Amazon Linux 2023.
  • yum install -y httpd installs Apache, the web server software.
  • systemctl start httpd starts Apache immediately.
  • systemctl enable httpd ensures Apache starts again if the instance reboots.
  • The echo line writes a simple HTML page to Apache's default document root.
  • Scroll back up and review the Summary panel on the right.
  • Click Launch instance.

You should see a success banner confirming that your instance is launching.

Why no key pair?

A key pair lets you SSH into the instance's terminal. For this project, you only need the web server to respond to HTTP requests. You will tear down this instance shortly, so SSH access is not necessary.

Getting an error when launching?

If you see a message about insufficient capacity, try waiting a few minutes and launching again. AWS occasionally runs low on specific instance types in a single availability zone.

If the security group does not appear in the dropdown, confirm that you selected devops-vpc as the VPC first. Security groups are VPC-specific.

Help me fix my EC2 launch error

Access your web page

Your instance needs about 60 seconds to boot, run the user data script, and start serving traffic.

  • Click Instances in the left navigation pane to see your instance list.
  • Wait until the Instance state column shows Running.
  • Wait until the Status check column shows 2/2 checks passed. This may take 1-2 minutes. Click the refresh icon if needed.
  • Select your devops-web-server instance by clicking its checkbox.
  • In the Details tab below, find the Public IPv4 address field and copy the IP address.

Before you open the URL, what do you think you will see? Will the page load successfully, or will it time out?

  • Open a new browser tab and navigate to http:// followed by the public IP you copied.
  • Make sure you use http:// and not https://.

You should see a page that says "Hello from the Console!" with a subtitle about your custom VPC. This confirms that your entire networking stack is working correctly.

Page not loading or timing out?

  • Make sure you are using http:// in the URL, not https://. Your security group only allows port 80 (HTTP), not port 443 (HTTPS).
  • Confirm the status checks show 2/2 checks passed. If the instance is still initializing, the user data script has not finished installing Apache yet.
  • Verify that devops-web-sg has an inbound rule for HTTP (port 80) from 0.0.0.0/0. Go to Security Groups, select your group, and check the Inbound rules tab.
  • Confirm your route table devops-public-rt has a route to 0.0.0.0/0 pointing at devops-igw. Without this route, traffic cannot leave the VPC.

Help me debug why my web page is not loading

Your web server is live. You built a complete cloud infrastructure from scratch: VPC, subnet, internet gateway, route table, security group, and EC2 instance. Next, you will tear it all down by hand to experience the pain that Terraform solves.

Tear It All Down Manually

Your web server is live and serving a page to anyone with the URL. The infrastructure works exactly as designed. Now you are going to delete every single resource you just built.

Why destroy working infrastructure? Because in the next step you will rebuild it all with Terraform. But there is a deeper reason. This step exists to make you feel the pain of manual resource management.

In this step, get ready to:
  • Terminate the EC2 instance and delete the security group.
  • Remove all networking resources in the correct dependency order.
  • Reflect on why this process motivates automation.
Terminate the EC2 instance

You must delete resources in reverse order of creation. If a resource depends on another resource, AWS will refuse to delete the dependency until the dependent resource is gone first.

  • In the AWS Console, navigate to the EC2 service.
  • Click Instances in the left sidebar.
  • Select the checkbox next to devops-web-server.
  • Click the Instance state dropdown at the top.
  • Select Terminate (delete) instance.
  • Click Terminate (delete) in the confirmation dialog.

The instance state changes to Shutting down and then Terminated. Wait until the state shows Terminated before moving on.

Why wait for terminated state?

The security group is still attached to the instance. AWS will not let you delete a security group that is in use by a running or shutting-down instance. You must wait for full termination before the security group becomes deletable.

Delete the security group
  • In the EC2 console left sidebar, click Security Groups (under the Network & Security section).
  • Select the checkbox next to devops-web-sg.
  • Click Actions, then select Delete security groups.
  • Type delete in the confirmation field and click Delete.

The security group disappears from the list. Two resources down, four to go.

Seeing a 'cannot delete' error on the security group?

Your EC2 instance has not fully terminated yet. Go back to Instances and confirm the state shows Terminated (not Shutting down). Wait another minute, then try deleting the security group again.

help me delete my security group in AWS

Remove the networking resources

Now you need to dismantle the VPC networking layer. The order matters here too: route table associations go first, then the route table, then the internet gateway, then the subnet, and finally the VPC itself.

  • Navigate to the VPC service in the AWS Console.
  • Click Route tables in the left sidebar.
  • Select devops-public-rt.
  • Click the Subnet associations tab at the bottom of the page.
  • Under Explicit subnet associations, click Edit subnet associations.
  • Uncheck devops-public-subnet and click Save associations.
  • Back on the route tables list, select devops-public-rt again, click Actions, then Delete route table.
  • Type delete to confirm and click Delete.

The route table is gone. Now for the internet gateway.

  • Click Internet gateways in the left sidebar.
  • Select devops-igw.
  • Click Actions, then Detach from VPC.
  • Click Detach internet gateway in the confirmation dialog.
  • With devops-igw still selected, click Actions again, then Delete internet gateway.
  • Type delete to confirm and click Delete.

Almost there. Two resources remain: the subnet and the VPC.

  • Click Subnets in the left sidebar.
  • Select devops-public-subnet.
  • Click Actions, then Delete subnet.
  • Type delete to confirm and click Delete.

Before you delete the VPC, think about this: do you expect the delete to succeed now that every dependent resource is gone?

  • Click Your VPCs in the left sidebar.
  • Select devops-vpc.
  • Click Actions, then Delete VPC.
  • Type delete to confirm and click Delete.

The VPC disappears from the list. Every resource you built in the previous steps is now gone.

VPC deletion failed?

AWS returns a dependency error if any resource still exists inside the VPC. Go back through the left sidebar and check that the internet gateway, route table, subnet, and security group are all deleted. The most common miss is forgetting to detach the internet gateway before trying to delete it.

help me delete my VPC in AWS

Reflect on what just happened

That process took several minutes of careful clicking in a strict order. You had to know the dependency chain. You had to wait for state changes. You had to navigate across multiple service pages.

Now imagine doing this for 50 resources across 3 environments (development, staging, production). This is exactly the problem that Infrastructure as Code solves.

Why does deletion order matter?

AWS resources form a dependency graph. A VPC cannot be deleted while it has subnets. A subnet cannot be deleted while instances run in it. An internet gateway cannot be deleted while attached to a VPC.

You have to unwind the graph from the leaves (most dependent) back to the root (least dependent). Terraform tracks this graph for you and deletes in the correct order automatically with a single terraform destroy command.

Your AWS account is now clean. Every resource from the manual build is gone. Next up, you will rebuild the entire stack with a single command using Terraform.

Rebuild Everything with Terraform

You just experienced the pain of manual infrastructure deletion. Several minutes of careful clicking, strict dependency ordering, and constant anxiety about missing a billable resource.

Now you will recreate the exact same infrastructure by writing code instead of clicking. Terraform lets you describe your entire stack in a configuration file, then create or destroy it with a single command.

In this step, get ready to:
  • Write a Terraform configuration that defines your VPC, subnet, internet gateway, route table, security group, and EC2 instance.
  • Run terraform plan to preview the infrastructure Terraform will create.
  • Run terraform apply and verify your web server is live in the browser.
Create your project folder
  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir devops-infra
cd devops-infra
  • Confirm the folder exists by running:
ls

You should see an empty folder listed. This is where your Terraform files will live.

Write the provider and data source configuration
  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type VS Code and press Enter to open it.
  • In VS Code, click File in the top menu bar.
  • Click Open Folder.
  • Select the devops-infra folder on your Desktop, then click Open.
  • In the Explorer sidebar, click the new file icon and name it main.tf.
  • Paste the following code into main.tf to set up the Terraform provider and AMI data source:
terraform {
  required_version = ">= 1.5"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

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

data "aws_ami" "amazon_linux" {
  most_recent = true
  owners      = ["amazon"]

  filter {
    name   = "name"
    values = ["al2023-ami-*-kernel-default-x86_64"]
  }

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

What does this code do?

This configuration has three parts:

  • The terraform block pins the minimum Terraform version and tells Terraform to download the AWS provider plugin (hashicorp/aws, version ~> 5.0).
  • The provider block configures which AWS region to use. All resources will be created in us-east-1.
  • The data block queries AWS for the latest Amazon Linux 2023 AMI. Instead of hardcoding an AMI ID that changes over time, Terraform looks it up dynamically.
  • Save main.tf.
  • Open a terminal in VS Code by pressing Ctrl+` (backtick key, top-left of your keyboard).
  • Download the AWS provider plugin by running:
terraform init

You should see output ending with:

Terraform has been successfully initialized!

What did terraform init do?

This command read your required_providers block, downloaded the hashicorp/aws plugin, and stored it in a .terraform/ folder inside your project. It also created a .terraform.lock.hcl file that pins the exact provider version.

Seeing a provider error?

  • Make sure your main.tf is saved before running the command.
  • Check that your terminal is in the devops-infra folder. Run cd ~/Desktop/devops-infra if needed.
  • Verify your internet connection is active. Terraform needs to download the provider from the registry.

Need more help? Why is terraform init failing with a provider error?

Add the networking resources

Now you will define the same VPC and subnet you created manually in Step 2, but this time as code that Terraform can reproduce on demand.

  • In main.tf, add the following code below the data source block to create the VPC and subnet:
resource "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_support   = true
  enable_dns_hostnames = true

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

resource "aws_subnet" "public" {
  vpc_id                  = aws_vpc.main.id
  cidr_block              = "10.0.1.0/24"
  availability_zone       = "us-east-1a"
  map_public_ip_on_launch = true

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

What does this code do?

  • aws_vpc.main creates the same 10.0.0.0/16 VPC you built by hand. The enable_dns_support and enable_dns_hostnames flags let instances get DNS names.
  • aws_subnet.public creates the 10.0.1.0/24 subnet in us-east-1a. The vpc_id = aws_vpc.main.id reference tells Terraform this subnet belongs to the VPC above. Terraform handles the dependency order automatically.
  • Still in main.tf, add the internet gateway, route table, and route table association below the subnet block:
resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id

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

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 = "devops-public-rt"
  }
}

resource "aws_route_table_association" "public" {
  subnet_id      = aws_subnet.public.id
  route_table_id = aws_route_table.public.id
}

What does this code do?

  • aws_internet_gateway.main attaches a gateway to your VPC so traffic can reach the public internet.
  • aws_route_table.public adds a route sending all outbound traffic (0.0.0.0/0) through the internet gateway.
  • aws_route_table_association.public links the route table to your subnet, so instances in that subnet can use the internet route.
  • Save main.tf and verify the configuration syntax by running:
terraform validate

You should see:

Success! The configuration is valid.

Seeing a validation error?

  • Check for missing closing braces } at the end of each resource block.
  • Make sure each resource reference (like aws_vpc.main.id) matches the resource name exactly as defined above.
  • Confirm there are no extra commas or typos in the attribute names.

Still stuck? Help me fix my Terraform validation error.

Add the security group and EC2 instance

The network is defined. Now you need the firewall rules and the web server itself.

  • In main.tf, add the security group below the route table association:
resource "aws_security_group" "web" {
  name        = "devops-web-sg"
  description = "Allow HTTP and SSH"
  vpc_id      = aws_vpc.main.id

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = 22
    to_port     = 22
    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 = "devops-web-sg"
  }
}

What does this code do?

  • The two ingress blocks allow inbound HTTP (port 80) and SSH (port 22) traffic from anywhere.
  • The egress block with protocol -1 allows all outbound traffic. This is the same default outbound rule you saw in the console.
  • vpc_id = aws_vpc.main.id places the security group inside your VPC so it can be attached to instances in that network.
  • Add the EC2 instance resource below the security group to complete main.tf:
resource "aws_instance" "web" {
  ami                    = data.aws_ami.amazon_linux.id
  instance_type          = "t3.micro"
  subnet_id              = aws_subnet.public.id
  vpc_security_group_ids = [aws_security_group.web.id]

  user_data = <<-EOF
              #!/bin/bash
              yum update -y
              yum install -y httpd
              systemctl start httpd
              systemctl enable httpd
              echo "<h1>Hello from Terraform!</h1><p>This infrastructure was built with code.</p>" > /var/www/html/index.html
              EOF

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

What does this code do?

  • ami uses the data source you defined earlier to dynamically fetch the latest Amazon Linux 2023 AMI ID.
  • vpc_security_group_ids attaches your firewall rules to this instance.
  • user_data is the same startup script from Step 3. It installs Apache and writes the HTML page. This time it says "Hello from Terraform!" to prove the server was built by code, not by clicking.
  • Save main.tf.
  • Create a second file in the Explorer sidebar called outputs.tf and paste the following:
output "web_server_public_ip" {
  description = "Public IP of the web server"
  value       = aws_instance.web.public_ip
}

output "web_server_url" {
  description = "URL to access the web server"
  value       = "http://${aws_instance.web.public_ip}"
}

What does outputs.tf do?

Output blocks tell Terraform to print specific values after terraform apply finishes. Instead of hunting through the AWS Console for your server's IP, Terraform hands it to you in the terminal.

  • Save outputs.tf.

✔️ Awesome, I've got everything!

Make sure both main.tf and outputs.tf are saved in your devops-infra folder.

ⓧ I'd like to double check the full code

Scroll up through the chunks you pasted and confirm your main.tf contains these sections in order:

  • Terraform block with required_version and required_providers.
  • Provider block with region = "us-east-1".
  • Data source aws_ami with two filters.
  • Resource aws_vpc with CIDR 10.0.0.0/16.
  • Resource aws_subnet with CIDR 10.0.1.0/24.
  • Resource aws_internet_gateway.
  • Resource aws_route_table with route to 0.0.0.0/0.
  • Resource aws_route_table_association.
  • Resource aws_security_group with HTTP and SSH ingress rules.
  • Resource aws_instance with user_data script.

Your outputs.tf should have two output blocks: web_server_public_ip and web_server_url.

Run terraform plan to preview your infrastructure

Before Terraform creates anything, it shows you a detailed preview of every resource it will add, change, or destroy.

  • Preview what Terraform will create by running:
terraform plan

Before you look at the output, predict: how many resources do you think Terraform will add? Count the resource blocks in your main.tf.

You should see a summary ending with:

Plan: 8 to add, 0 to change, 0 to destroy.

What does terraform plan show?

Terraform read both your .tf files and compared the desired state (your code) to the current state (nothing exists yet). The plan shows it needs to create 8 resources from scratch. No changes, no deletions.

The plan does not create anything in AWS. It is a dry run that lets you verify before committing.

Seeing fewer resources or an error?

  • If you see a reference error like "resource not found", check that your resource names match exactly (aws_vpc.main, aws_subnet.public, etc.).
  • If the plan shows fewer than 8, confirm all resource blocks from the chunks above are present in main.tf.
  • If you see a credentials error, verify your AWS CLI is still configured by running aws sts get-caller-identity.

Need more help? Why is my terraform plan failing or showing the wrong resource count?

Apply the configuration and verify your web server

The plan looks correct. Time to let Terraform build everything.

  • Create all 8 resources in AWS by running:
terraform apply

What will terraform apply ask?

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

  • Type yes when prompted and press Enter.

Terraform creates all 8 resources in about 60 seconds. When it finishes, you will see your outputs printed in the terminal, including web_server_url.

  • Copy the web_server_url value from the terminal output.
  • Open that URL in your browser.

Do you think the page will load immediately, or might the server need a moment to start? Make your prediction before checking.

You should see the page displaying "Hello from Terraform!" with the subtitle "This infrastructure was built with code." If the page does not load immediately, wait 30-60 seconds for the user data script to finish installing Apache.

What just happened?

In about 60 seconds, Terraform created the exact same infrastructure that took you several minutes of clicking through multiple AWS console pages. Every resource is defined in code, version-controlled, and reproducible.

Terraform also created a terraform.tfstate file in your devops-infra folder. This file tracks the real-world IDs of everything Terraform manages, so it knows what to update or destroy later.

Page not loading?

  • Wait at least 60 seconds after terraform apply completes. The EC2 instance needs time to boot and run the user data script.
  • Make sure you are using http:// not https:// in the URL. The server only listens on port 80.
  • If the page still does not load after 2 minutes, check the EC2 console to confirm the instance is in the "running" state and has a public IP assigned.

Still stuck? My terraform apply succeeded but I cannot reach the web page.

Your entire infrastructure now exists as code. In the next section, you can tear it all down with a single terraform destroy command. No more clicking through console pages in reverse dependency order.

Secret mission

Add a Second Web Server in Another Availability Zone

Real production infrastructure runs across multiple availability zones for redundancy. Can you extend your Terraform configuration to deploy a second web server in a different zone, proving how code makes scaling trivial?

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. The EC2 instances in this project are covered by AWS Free Tier credits for new accounts, but they do consume those credits while running.

Cost warning

Each running t3.micro instance consumes your AWS Free Tier credits. If your credits are exhausted, standard EC2 charges apply. The VPC networking components (subnets, route tables, internet gateway) have no per-resource charge.

If you are not actively working on this project, choose Pause or Delete below to avoid unnecessary credit usage.

Resources you used:

  • AWS VPC (devops-vpc) with subnets, internet gateway, and route table.
  • AWS security group (devops-web-sg).
  • AWS EC2 instances (devops-web-server-1 and devops-web-server-2 if you completed the Secret Mission).
  • Local project folder (devops-infra) with Terraform configuration files.
  • AWS CLI installed on your machine.
  • Terraform installed on your machine.

Keep everything running

No action needed. Choose this if you are still actively building on this project or want to experiment further with Terraform.

  • Your EC2 instances will continue consuming AWS Free Tier credits while running.
  • Monitor your credit usage in the AWS Billing dashboard under Free Tier.
  • The AWS CLI and Terraform remain installed on your machine for future projects.

Pause - I'll come back to this later

Stop the EC2 instances to preserve credits while keeping your Terraform files and AWS networking in place.

  • In the AWS Console, go to EC2 > Instances.
  • Select your running instance(s), then click Instance state > Stop instance.
  • Stopped instances do not consume compute credits. EBS storage still counts against credits but is minimal for this project.
  • To resume later, select the instance(s) and choose Instance state > Start instance.
  • Your local devops-infra folder stays on your Desktop for when you return.

Delete - I don't want to use this again

Remove all project resources and start fresh. This leaves no running costs and no lingering infrastructure.

Destroy all AWS resources with Terraform:

  • Open PowerShell and navigate to your project folder by running:
cd ~/Desktop/devops-infra
  • Destroy all AWS resources by running:
terraform destroy
  • Type yes when prompted. Terraform removes all 11 resources (VPC, subnets, gateway, route table, associations, security group, and EC2 instances) in under a minute.

Delete local project files:

  • Remove the project folder by running:
cd ~/Desktop
Remove-Item -Recurse -Force devops-infra

Uninstall Terraform:

  • Delete the folder where you extracted terraform.exe (e.g., C:\terraform).
  • Remove that folder from your system PATH environment variable.

Uninstall AWS CLI:

  • Open Settings > Apps > Installed apps.
  • Find AWS Command Line Interface v2 and click Uninstall.
  • Optionally, delete the .aws folder in your user profile to remove stored credentials.

That's a Wrap!

That's a Wrap!

Nice work! You just built a live web server on AWS twice: once by hand to understand every networking piece, and once with Terraform to see how professionals manage infrastructure at scale.

You've learned how to:

  • Built a complete VPC network from scratch in the AWS Console, connecting a subnet, internet gateway, route table, and security group to give a server internet access.
  • Launched an EC2 web server with a user data script that serves a live page you can hit from your browser.
  • Wrote a Terraform configuration that recreates the entire networking stack and web server with a single terraform apply command, and tears it all down with terraform destroy.
  • Secret Mission: Extended the infrastructure to multiple availability zones for redundancy, proving how Terraform makes scaling trivial.

Ready to quiz yourself?