Build an AWS VPC with Terraform
Build a two-AZ AWS VPC with public and private subnets using Terraform.
Introduction
30 Second Summary
Starting every new cloud project from a blank page makes it easy to miss a critical connection. A reusable foundation gives everything that follows a predictable place to live.
In this project, you will use Terraform to build a reusable VPC on AWS across two Availability Zones. You will prove how routing controls public access before exporting the identifiers that later storage and compute projects can reuse.
What You'll Build
You will run Terraform to watch AWS create a two-tier network whose public route can be inspected directly from your terminal.
By the end of this project, you'll have:
- A repeatable launch workflow that creates one VPC with separate public and private subnet tiers across two Availability Zones.
- Route-based public connectivity you can inspect to show why the public subnets can reach an internet gateway.
- Reusable Terraform outputs that return the VPC, subnet, Availability Zone, and route table IDs directly in PowerShell.
- Secret Mission: Replace the shared private route table with one private route table per Availability Zone.
Are there any prerequisites?
You need a Windows computer with access to an AWS account through an authorized non-root identity that can manage VPC networking resources. Step 1 guides you through local tool setup before checking that identity.
Before We Start
This step is your moment to commit to a Terraform-managed two-Availability-Zone VPC foundation before the hands-on work begins. That network will support private Amazon S3 access and an Amazon EC2 workload in later projects.
Set Up Your Windows Terraform Workstation
Your VPC design needs a trustworthy control point before it can reach AWS. Terraform can manage the network safely once your workstation can run the required CLI release.
First, you will install the required tools. Then you will connect the project folder to an authorized AWS profile.
In this step, get ready to:
- Install Terraform CLI 1.16.2 on PATH.
- Install AWS CLI 2.36.46 on PATH.
- Prepare an authenticated Visual Studio Code workspace.
Install Terraform and the AWS CLI
Windows PowerShell runs every command in this project. Windows uses PATH to find installed executables such as terraform.exe.
- Press the Windows key to open Windows search.
- Type Windows PowerShell.
- Press Enter to open PowerShell.
You should see a PowerShell window with a command prompt ready for input.
The first checks reveal whether the required executables already exist on PATH. A missing command is an expected setup state here.
- Check both installed versions by running these commands:
terraform -version
aws --version
What Do These Commands Check?
- The first command asks Terraform to print its installed version.
- The second command asks the AWS CLI to print its installed version.
Your Terraform result determines which setup path applies.
✔️ I see version 1.16.2
Terraform is already installed at the required version. Your current PATH can find it.
ⓧ I see an older version
Your existing Terraform executable needs to be replaced with version 1.16.2.
- Open the official Terraform install page.
- Download the Windows AMD64 archive for Terraform 1.16.2.
- Extract terraform.exe into a dedicated folder named C:\terraform.
The Windows PATH controls which folders PowerShell searches for executables. Its settings sit several screens deep, so this part can feel fiddly.
- Press the Windows key.
- Type Control Panel.
- Press Enter.
- Select System.
- Select Advanced System Settings.
- Select the Advanced tab.
- Select Environment Variables.
- Select Path under User variables.
You are now editing the search path for your Windows account.
- Select Edit.
- Add C:\terraform as a new entry.
- Select OK in every open dialog.
Cannot Add Terraform to PATH?
Confirm that terraform.exe sits directly inside C:\terraform. Add the folder path without the executable name.
Ask for help with the Windows setting if the entry does not save: help me add C:\terraform to my Windows user PATH
ⓧ Command not found
Terraform is not installed yet. The Windows binary contains the executable you need.
- Open the official Terraform install page.
- Download the Windows AMD64 archive for Terraform 1.16.2.
- Extract terraform.exe into a dedicated folder named C:\terraform.
Windows hides the PATH editor inside the System Control Panel. Adding the folder there makes Terraform available to new PowerShell sessions.
- Press the Windows key.
- Type Control Panel.
- Press Enter.
- Select System.
- Select Advanced System Settings.
- Select the Advanced tab.
- Select Environment Variables.
- Select Path under User variables.
The remaining controls add your Terraform folder to the search path.
- Select Edit.
- Add C:\terraform as a new entry.
- Select OK in every open dialog.
Terraform Still Missing?
Check that the downloaded archive has been extracted. PowerShell cannot run terraform.exe while it remains inside the archive.
Get help checking the executable location: help me find why PowerShell cannot locate terraform.exe
Your AWS CLI result determines the second setup path.
✔️ I see version 2.36.46
The AWS CLI is already installed at version 2.36.46.
ⓧ I see an older version
Install AWS CLI 2.36.46 so this project uses the verified release.
- Install the current AWS CLI release by running this command:
irm https://awscli.amazonaws.com/v2/install.ps1 | iex
Installation Command Fails?
This command downloads the Windows installation script from AWS. It then runs the installer in PowerShell.
Check your network connection if the download stops. Then ask for targeted help: help me troubleshoot the AWS CLI Windows installation command
ⓧ Command not found
The AWS CLI is not installed yet. The official PowerShell installer adds it to your Windows workstation.
- Install AWS CLI 2.36.46 by running this command:
irm https://awscli.amazonaws.com/v2/install.ps1 | iex
Installer Did Not Finish?
The command downloads the AWS CLI installer from AWS. It runs that installer in the current PowerShell session.
Confirm that Windows permits software installation for your account. Ask for help if the installer remains blocked: help me complete the AWS CLI installation on Windows
A fresh PowerShell session loads the updated PATH.
- Close the current PowerShell window.
- Press the Windows key.
- Type Windows PowerShell.
- Press Enter.
Before you rerun the checks, predict whether the fresh session can find both executables. Keep that prediction in mind.
- Verify both installed versions by running:
terraform -version
aws --version
Why Use a Fresh Window?
A new PowerShell process reads the latest PATH settings. These checks prove that future project commands can find both tools.
You should see Terraform version 1.16.2. You should also see AWS CLI version 2.36.46.
That is the first layer ready. Your terminal can now run the exact tool versions used throughout this project.
Install VS Code and create the project folder
Visual Studio Code gives your Terraform files one visible workspace. Its Windows User setup also makes the code command available to fresh consoles.
- Press the Windows key.
- Type Visual Studio Code.
- Check whether Visual Studio Code appears in the search results.
✔️ Visual Studio Code appears
Visual Studio Code is already installed. Its command-line launcher is checked when you open the project folder below.
ⓧ Visual Studio Code is missing
The Windows User setup opens an installation wizard. Keep its option for adding Visual Studio Code to PATH enabled.
- Open the official Visual Studio Code Windows setup guide.
- Download the Windows User setup installer.
- Run the downloaded installer.
- Keep the option that adds Visual Studio Code to PATH selected.
- Complete the installation wizard.
Installer Blocked?
Confirm that you downloaded the Windows User setup from the official Visual Studio Code page. Ask for help if Windows prevents the installer from starting: help me install Visual Studio Code User setup on Windows
A dedicated project folder keeps the Terraform configuration beside its future local state. Placing it on your Desktop makes it easy to find during this series.
- Return to the fresh PowerShell window from earlier.
- Move to your Desktop by running this command:
cd ~/Desktop
What Does This Command Do?
The cd command changes PowerShell's current location. The tilde points to your Windows home folder.
The next command sequence creates the workspace. Visual Studio Code opens the folder at the end.
- Create the project folder and open it in Visual Studio Code by running:
mkdir terraform-vpc-foundation
cd terraform-vpc-foundation
code .
What Does This Command Sequence Do?
- The first command creates the terraform-vpc-foundation folder.
- The second command moves PowerShell into that folder.
- The final command opens the current folder in Visual Studio Code.
You should see Visual Studio Code open with terraform-vpc-foundation at the top of the Explorer sidebar. The folder should be empty because no Terraform configuration exists yet.
VS Code Did Not Open?
Close PowerShell after the Visual Studio Code installation. Open a fresh PowerShell window so it loads the updated PATH.
Return to the terraform-vpc-foundation folder before retrying. Get help if the launcher still fails: help me fix the code command on Windows
Configure the AWS profile and verify your workstation
A named profile keeps this project's AWS identity separate from other accounts on your computer. Terraform uses the same profile in later steps.
Credential entry can feel sensitive. The named profile keeps these values outside your Terraform configuration.
What Will the Command Ask For?
The configuration command pauses for your access key ID. It then asks for the matching secret access key.
The command also asks for a default Region. This project uses us-east-1.
The final prompt asks for an optional output format. You can leave that value empty.
- Gather the credentials issued to your authorized non-root learning identity.
- Start the named profile configuration by running:
aws configure --profile terraform-learning
What Does This Command Do?
The command creates or updates the named AWS CLI profile terraform-learning. The profile lets later commands select the intended identity explicitly.
- Paste the access key ID at the first prompt.
- Paste the matching secret access key at the second prompt.
- Enter us-east-1 at the default Region prompt.
- Press Enter to leave the optional output format empty.
Keep Credentials Outside Terraform
Never paste these credentials into a .tf file. Do not share or screenshot the credential prompts.
Before the final check, predict whether all three commands can now identify their tool or AWS identity. Keep that prediction in mind.
- Verify the complete workstation setup by running:
terraform -version
aws --version
aws sts get-caller-identity --profile terraform-learning
What Does the Final Check Prove?
- The first command confirms that PowerShell can run Terraform from PATH.
- The second command confirms that PowerShell can run the AWS CLI from PATH.
- The final command asks AWS which identity belongs to the terraform-learning profile.
You should see Terraform version 1.16.2. You should also see AWS CLI version 2.36.46.
The JSON response should contain your intended AWS account and ARN. That is your workstation fully connected to the correct AWS identity.
Identity Check Failed?
Confirm that the profile name is exactly terraform-learning. Reconfigure the profile if an incorrect access key was entered.
Check with the person who issued the credentials if AWS rejects them. Ask for help without sharing any credential values: help me troubleshoot the caller identity check
Your Windows workstation can now run Terraform against the intended AWS account. Next, you will define the VPC and its four-subnet address layout.
Launch the VPC and Subnet Foundation
Your Windows workstation now has an authenticated AWS profile ready for infrastructure work. The next job is to turn that access into a repeatable network definition.
A subnet can carry a public label while remaining limited to local routing. In this step, your Terraform configuration deliberately builds the address layout without an internet path so you can prove that distinction in state.
In this step, get ready to:
- Define the Terraform version constraint, AWS provider constraint, shared variables, and provider settings.
- Build one VPC with four subnets across two Availability Zones.
- Initialize, review, apply, and inspect the Terraform configuration.
Define Terraform settings and shared values
Terraform reads version constraints before it loads the provider. Variables keep your Region, profile, project name, and environment reusable across the configuration.
- Click the New File icon in the Explorer sidebar in Visual Studio Code.
- Enter versions.tf as the file name.
- Add the Terraform and AWS provider constraints by pasting this configuration:
terraform {
required_version = "1.16.2"
required_providers {
aws = {
source = "hashicorp/aws"
version = "6.64.0"
}
}
}
How Do the Constraints Help?
- The required_version value keeps this project on Terraform 1.16.2.
- The required_providers block tells Terraform to install hashicorp/aws version 6.64.0.
- Save versions.tf by pressing Ctrl+S.
- Confirm that versions.tf appears in the Explorer sidebar.
- Click the New File icon in the Explorer sidebar.
- Enter variables.tf as the file name.
- Declare the Region variable by pasting this block:
variable "aws_region" {
description = "AWS Region for the VPC foundation"
type = string
}
What Does the Region Variable Hold?
The aws_region variable holds the AWS Region used by the provider. Its string type requires a text value.
- Save variables.tf by pressing Ctrl+S.
- Confirm that the file contains the aws_region variable block.
- Place your cursor below the aws_region block in variables.tf.
- Declare the named profile variable by pasting this block:
variable "aws_profile" {
description = "Named AWS CLI profile used by Terraform"
type = string
}
What Does the Profile Variable Hold?
The aws_profile variable identifies the named AWS CLI profile that Terraform uses for authentication.
- Save variables.tf.
- Confirm that aws_profile appears below aws_region.
- Place your cursor below the aws_profile block.
- Declare the project name variable by pasting this block:
variable "project_name" {
description = "Name used to tag the VPC foundation"
type = string
}
Why Store the Project Name?
The project_name variable supplies a consistent value for resource tags. That value makes the network easier to identify in AWS.
- Save variables.tf.
- Confirm that project_name appears as the third variable.
- Place your cursor below the project_name block.
- Declare the environment variable by pasting this block:
variable "environment" {
description = "Environment tag applied to managed AWS resources"
type = string
}
Why Use an Environment Value?
The environment variable separates development resources from other environments through tags and names.
- Save variables.tf.
- Confirm that variables.tf contains four variable blocks.
- Click the New File icon in the Explorer sidebar.
- Enter terraform.tfvars as the file name.
- Assign the project values by pasting this configuration:
aws_region = "us-east-1"
aws_profile = "terraform-learning"
project_name = "terraform-vpc-foundation"
environment = "dev"
What Do These Values Control?
- The Region value places the network in us-east-1.
- The profile value selects terraform-learning for authentication.
- The project value supplies terraform-vpc-foundation for names and tags.
- The environment value labels this first deployment as dev.
- Save terraform.tfvars.
- Confirm that all four assignments appear in the file.
- Click the New File icon in the Explorer sidebar.
- Enter providers.tf as the file name.
- Configure the AWS provider by pasting this block:
provider "aws" {
region = var.aws_region
profile = var.aws_profile
default_tags {
tags = {
Project = var.project_name
Environment = var.environment
ManagedBy = "Terraform"
}
}
}
How Does the Provider Work?
- The provider reads its Region and profile from the values in terraform.tfvars.
- The default_tags block applies shared tags to supported resources.
- The ManagedBy tag records Terraform as the infrastructure manager.
- Save providers.tf.
- Confirm that the Explorer sidebar lists all four configuration files.
Configuration Files Look Wrong?
Check that each filename ends with the expected .tf or .tfvars extension. Compare every opening brace with its closing brace.
Use this if your files still look different: help me compare my Terraform settings files with the required configuration.
Build the VPC address layout
The network uses one CIDR range for the VPC. Four smaller ranges create distinct public and private tiers across two zones.
- Click the New File icon in the Explorer sidebar.
- Enter vpc.tf as the file name.
- Select two standard Availability Zones by pasting this data source:
data "aws_availability_zones" "available" {
state = "available"
filter {
name = "opt-in-status"
values = ["opt-in-not-required"]
}
}
How Are the Zones Selected?
- The data source asks AWS for Availability Zones that are currently available.
- The filter keeps standard zones that require no account opt-in.
- The subnet resources use the first two names returned by this data source.
- Save vpc.tf.
- Confirm that the Availability Zones data source appears at the top of the file.
- Place your cursor below the Availability Zones data source in vpc.tf.
- Define the VPC by pasting this resource block:
resource "aws_vpc" "main" {
cidr_block = "10.42.0.0/16"
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "${var.project_name}-${var.environment}"
}
}
What Does the VPC Provide?
- The 10.42.0.0/16 range supplies the address space for every subnet in this foundation.
- DNS support enables name resolution inside the VPC.
- DNS hostnames allow eligible resources to receive DNS hostnames.
- The Name tag combines the project and environment values.
- Save vpc.tf.
- Confirm that the VPC block shows 10.42.0.0/16 with both DNS settings enabled.
- Place your cursor below the aws_vpc.main resource.
- Define the first public subnet by pasting this resource block:
resource "aws_subnet" "public_a" {
vpc_id = aws_vpc.main.id
cidr_block = "10.42.0.0/24"
availability_zone = data.aws_availability_zones.available.names[0]
map_public_ip_on_launch = true
tags = {
Name = "${var.project_name}-${var.environment}-public-a"
Tier = "public"
}
}
How Is the First Public Subnet Defined?
- The subnet belongs to aws_vpc.main.
- The 10.42.0.0/24 range gives this subnet its own non-overlapping address space.
- The subnet uses the first selected Availability Zone.
- The public-IP setting allows eligible launched resources to request public addresses.
- Save vpc.tf.
- Confirm that public_a uses the first zone and the 10.42.0.0/24 range.
- Place your cursor below the aws_subnet.public_a resource.
- Define the second public subnet by pasting this resource block:
resource "aws_subnet" "public_b" {
vpc_id = aws_vpc.main.id
cidr_block = "10.42.1.0/24"
availability_zone = data.aws_availability_zones.available.names[1]
map_public_ip_on_launch = true
tags = {
Name = "${var.project_name}-${var.environment}-public-b"
Tier = "public"
}
}
How Does the Second Public Subnet Differ?
- The 10.42.1.0/24 range stays separate from the first public subnet.
- The subnet uses the second selected Availability Zone.
- The shared Tier tag identifies its intended role.
- Save vpc.tf.
- Confirm that public_b uses the second zone and the 10.42.1.0/24 range.
- Place your cursor below the aws_subnet.public_b resource.
- Define the first private subnet by pasting this resource block:
resource "aws_subnet" "private_a" {
vpc_id = aws_vpc.main.id
cidr_block = "10.42.10.0/24"
availability_zone = data.aws_availability_zones.available.names[0]
map_public_ip_on_launch = false
tags = {
Name = "${var.project_name}-${var.environment}-private-a"
Tier = "private"
}
}
How Is the First Private Subnet Isolated?
- The 10.42.10.0/24 range stays separate from both public ranges.
- The subnet shares the first Availability Zone with public_a.
- The disabled public-IP setting keeps automatic public address assignment off.
- Save vpc.tf.
- Confirm that private_a uses the first zone and has public-IP mapping disabled.
- Place your cursor below the aws_subnet.private_a resource.
- Define the second private subnet by pasting this resource block:
resource "aws_subnet" "private_b" {
vpc_id = aws_vpc.main.id
cidr_block = "10.42.11.0/24"
availability_zone = data.aws_availability_zones.available.names[1]
map_public_ip_on_launch = false
tags = {
Name = "${var.project_name}-${var.environment}-private-b"
Tier = "private"
}
}
How Does the Final Subnet Complete the Layout?
- The 10.42.11.0/24 range completes the non-overlapping address plan.
- The subnet shares the second Availability Zone with public_b.
- The VPC now has one public and one private subnet in each selected zone.
- Save vpc.tf.
- Confirm that the file contains one VPC and four subnet resources.
Subnet Blocks Do Not Line Up?
Check that each subnet has a unique CIDR range. Confirm that the zone indexes use names[0] for the A subnets and names[1] for the B subnets.
Use this for a focused comparison: help me check my VPC and subnet blocks for mismatched CIDR ranges or zone indexes.
✔️ Awesome, I've got everything!
Your five Terraform files now describe the complete VPC and subnet foundation for this step. Make sure every file is saved before continuing.
ⓧ I'd like to double check the full code
- Compare each saved file with the matching reference below.
provider "aws" {
region = var.aws_region
profile = var.aws_profile
default_tags {
tags = {
Project = var.project_name
Environment = var.environment
ManagedBy = "Terraform"
}
}
}
aws_region = "us-east-1"
aws_profile = "terraform-learning"
project_name = "terraform-vpc-foundation"
environment = "dev"
variable "aws_region" {
description = "AWS Region for the VPC foundation"
type = string
}
variable "aws_profile" {
description = "Named AWS CLI profile used by Terraform"
type = string
}
variable "project_name" {
description = "Name used to tag the VPC foundation"
type = string
}
variable "environment" {
description = "Environment tag applied to managed AWS resources"
type = string
}
terraform {
required_version = "1.16.2"
required_providers {
aws = {
source = "hashicorp/aws"
version = "6.64.0"
}
}
}
data "aws_availability_zones" "available" {
state = "available"
filter {
name = "opt-in-status"
values = ["opt-in-not-required"]
}
}
resource "aws_vpc" "main" {
cidr_block = "10.42.0.0/16"
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "${var.project_name}-${var.environment}"
}
}
resource "aws_subnet" "public_a" {
vpc_id = aws_vpc.main.id
cidr_block = "10.42.0.0/24"
availability_zone = data.aws_availability_zones.available.names[0]
map_public_ip_on_launch = true
tags = {
Name = "${var.project_name}-${var.environment}-public-a"
Tier = "public"
}
}
resource "aws_subnet" "public_b" {
vpc_id = aws_vpc.main.id
cidr_block = "10.42.1.0/24"
availability_zone = data.aws_availability_zones.available.names[1]
map_public_ip_on_launch = true
tags = {
Name = "${var.project_name}-${var.environment}-public-b"
Tier = "public"
}
}
resource "aws_subnet" "private_a" {
vpc_id = aws_vpc.main.id
cidr_block = "10.42.10.0/24"
availability_zone = data.aws_availability_zones.available.names[0]
map_public_ip_on_launch = false
tags = {
Name = "${var.project_name}-${var.environment}-private-a"
Tier = "private"
}
}
resource "aws_subnet" "private_b" {
vpc_id = aws_vpc.main.id
cidr_block = "10.42.11.0/24"
availability_zone = data.aws_availability_zones.available.names[1]
map_public_ip_on_launch = false
tags = {
Name = "${var.project_name}-${var.environment}-private-b"
Tier = "private"
}
}
Initialize and launch the foundation
The configuration is ready to move through Terraform's workflow. Formatting makes the files consistent before initialization installs the pinned provider.
- Format every Terraform configuration file by running this command in the PowerShell window from earlier:
terraform fmt
What Does Formatting Change?
The command rewrites Terraform configuration files into canonical style. Consistent alignment makes later plan reviews easier to scan.
- Return to Visual Studio Code.
- Confirm that the assignments and nested blocks use consistent indentation.
The first initialization can take about a minute while Terraform installs the provider. A brief pause here is expected.
- Initialize the terraform-vpc-foundation folder by running this command in PowerShell:
terraform init
What Does Initialization Prepare?
Initialization installs the required provider and prepares the local working directory. It also writes the provider dependency lock file.
PowerShell reports that the working directory finished initializing. The project is now ready for validation and planning.
Initialization Did Not Finish?
Confirm that PowerShell is still inside the terraform-vpc-foundation folder. Check that your internet connection can reach the provider registry.
Use this if the provider installation still fails: help me troubleshoot Terraform initialization on Windows.
- Check the configuration for syntax and internal consistency by running:
terraform validate
What Does Validation Check?
Validation checks the configuration syntax and references between blocks. A successful result proves that Terraform can understand the local definition.
PowerShell confirms that the configuration is valid.
Validation Found a Problem?
Use the filename and line number in the validation result to locate the affected block. Compare the variable name or brace at that location with the full-code reference above.
Use this for help with the reported location: help me fix my Terraform validation issue.
Before you preview the deployment, how many AWS resources do you expect Terraform to propose from one VPC block and four subnet blocks?
- Preview the proposed infrastructure by running:
terraform plan
What Should the Plan Contain?
The plan proposes one VPC and four subnets. It also reads the selected Availability Zones before assigning the subnet locations.
The proposal contains no internet gateway, explicit route table, or public default route.
- Review the plan for one VPC and four subnet resources.
- Confirm that every proposed resource uses the expected project and environment values.
Before you apply the plan, do you expect the public labels and public-IP settings to create an internet route?
Terraform pauses for approval after displaying the plan. The reviewed configuration creates the cost-conscious network resources defined in your files.
- Launch the VPC and subnet foundation by running:
- Enter yes when PowerShell asks for approval.
terraform apply
What Does Apply Do?
Terraform creates the approved resources in AWS. It records their identifiers and relationships in local state.
That is the first deployment complete. PowerShell reports that five resources were added to your AWS account.
Before the final check, do you expect the state to contain an internet gateway or a default internet route?
- Inspect every tracked object by running:
terraform state list
What Should State Contain?
- The Availability Zones data source appears as data.aws_availability_zones.available.
- The VPC appears as aws_vpc.main.
- The first public subnet appears as aws_subnet.public_a.
- The second public subnet appears as aws_subnet.public_b.
- The first private subnet appears as aws_subnet.private_a.
- The second private subnet appears as aws_subnet.private_b.
The two public-named subnets now exist in AWS. The tracked configuration contains no internet gateway, explicit route table, or public default route, which proves that the labels alone provide no internet path.
State List Missing a Resource?
Scroll back to the apply result and check whether all five resources completed. Compare any missing address with the matching resource block in vpc.tf.
Use this to investigate a missing state entry: help me diagnose why Terraform state is missing a VPC or subnet resource.
Your two-zone address layout is live and tracked by Terraform. Next, you will add the routing resources that turn the public tier into a genuinely routed tier.
Make the Public Tier Actually Public
Your two-AZ VPC now holds four subnets with a planned address layout. Terraform state proves those resources exist.
Public connectivity depends on a route to an internet gateway. The current public labels leave that path missing.
The automatic public IP setting also leaves routing unchanged. You will inspect that gap before adding a public route table.
In this step, get ready to:
- Inspect the current public subnet state.
- Build the missing public routing path.
- Prove the deployed default route through Terraform state.
Inspect the unrouted public subnet
A subnet resource records its own network settings. Its state does not contain a route table association unless Terraform manages that relationship separately.
Before you check, do you expect the subnet state to show a route to the internet?
- In the Windows PowerShell window from earlier, inspect the first public subnet by running this command:
terraform state show aws_subnet.public_a
What does this command show?
- The command reads the deployed attributes for aws_subnet.public_a from Terraform state.
- The result lets you check the subnet before changing its routing.
You will see the subnet attributes from the previous deployment. The output has no route table association or default internet route.
That shortfall is intentional. The subnet name and public IP setting have not created an internet path.
Subnet state unavailable?
- Confirm PowerShell is still using the initialized terraform-vpc-foundation folder.
- Compare aws_subnet.public_a with the resource address from the previous state list.
Help me troubleshoot why Terraform cannot show the existing public subnet state.
Build the public internet path
The public path needs three layers. An attached gateway provides the edge connection. A default route directs internet-bound traffic to that gateway.
Explicit associations place both public subnets under the same routing rules. The private subnets remain untouched.
- In the VS Code Explorer sidebar, create network.tf inside the open terraform-vpc-foundation folder.
- Paste this internet gateway resource into network.tf:
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = {
Name = "${var.project_name}-${var.environment}-igw"
}
}
How does the gateway fit?
- The vpc_id reference attaches the gateway to aws_vpc.main.
- The Name tag combines the current project value with the current environment value.
- Save network.tf.
- Validate the gateway configuration from the PowerShell window by running this command:
terraform validate
What does this check prove?
Terraform checks that the new gateway resource is internally consistent. A successful result confirms that its VPC reference resolves.
Your first public-routing dependency is now wired correctly.
Gateway validation failed?
- Compare the resource block with the snippet above.
- Confirm network.tf is saved inside terraform-vpc-foundation.
Help me find the validation problem in my internet gateway resource.
- Add this public route table below the gateway resource in network.tf:
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
tags = {
Name = "${var.project_name}-${var.environment}-public-rt"
Tier = "public"
}
}
What does the route table provide?
- The route table belongs to the existing VPC.
- The Tier tag identifies its public routing role.
- Save network.tf.
- Validate the route table configuration by running this command:
terraform validate
What does this validation confirm?
Terraform can now resolve the public route table against the existing VPC. The route table is ready to receive a route.
That is the public routing container ready for its internet path.
Route table validation failed?
- Check the braces around the tags block.
- Confirm the resource name is aws_route_table.public.
Help me diagnose the route table validation failure in network.tf.
- Add this default route below the public route table in network.tf:
resource "aws_route" "public_internet" {
route_table_id = aws_route_table.public.id
destination_cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
How does this route work?
- The route_table_id places the route inside the public route table.
- The 0.0.0.0/0 destination covers IPv4 addresses outside more specific routes.
- The gateway_id sends matching traffic to the managed internet gateway.
- Save network.tf.
- Validate the default route by running this command:
terraform validate
What does this validation confirm?
Terraform can resolve both ends of the route. The public route table points to the managed internet gateway in configuration.
The missing internet path now passes Terraform's static check.
Default route validation failed?
- Confirm route_table_id references aws_route_table.public.id.
- Confirm gateway_id references aws_internet_gateway.main.id.
Help me troubleshoot the public default route in network.tf.
- Add this association below the public route in network.tf:
resource "aws_route_table_association" "public_a" {
subnet_id = aws_subnet.public_a.id
route_table_id = aws_route_table.public.id
}
What does this association change?
- The association selects aws_subnet.public_a.
- That subnet now follows the rules in aws_route_table.public.
- Save network.tf.
- Validate the first subnet association by running this command:
terraform validate
What does this validation confirm?
Terraform can resolve the first public subnet and the public route table. Their association is valid in configuration.
The first public subnet is now connected to the intended route table.
First association validation failed?
- Confirm subnet_id references aws_subnet.public_a.id.
- Check that the association resource is named public_a.
Help me fix the first public subnet association in network.tf.
- Add this second association below the first association in network.tf:
resource "aws_route_table_association" "public_b" {
subnet_id = aws_subnet.public_b.id
route_table_id = aws_route_table.public.id
}
Why add a second association?
- The association selects aws_subnet.public_b.
- Both public subnets now share one explicit public route table.
- Save network.tf.
- Validate the completed routing configuration by running this command:
terraform validate
What does the completed check prove?
Terraform can resolve the gateway, route table, route, and both subnet associations. The private subnets have no new associations.
Both public subnets are now wired to one public route table in configuration.
Completed configuration still invalid?
- Compare the second association with the first association.
- Confirm the second subnet_id references aws_subnet.public_b.id.
Help me find the remaining validation issue in my completed network.tf file.
✔️ Awesome, I've got everything!
Your network.tf file now contains the complete public routing configuration. Make sure the file is saved.
ⓧ I'd like to double check the full code
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = {
Name = "${var.project_name}-${var.environment}-igw"
}
}
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
tags = {
Name = "${var.project_name}-${var.environment}-public-rt"
Tier = "public"
}
}
resource "aws_route" "public_internet" {
route_table_id = aws_route_table.public.id
destination_cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
resource "aws_route_table_association" "public_a" {
subnet_id = aws_subnet.public_a.id
route_table_id = aws_route_table.public.id
}
resource "aws_route_table_association" "public_b" {
subnet_id = aws_subnet.public_b.id
route_table_id = aws_route_table.public.id
}
How should the file compare?
This reference contains the exact five resources introduced in this step. Their order matches the dependency path from gateway to subnet associations.
Apply and verify the public route
Terraform can now compare the routing configuration with the deployed AWS network. The plan should contain only the five new resources from network.tf.
- Format the Terraform files by running this command:
terraform fmt
What does formatting do?
Terraform rewrites the configuration into its canonical style. This keeps resource blocks consistent before validation.
Your routing file now follows Terraform's standard layout.
- Validate the formatted configuration by running this command:
terraform validate
What does this validation prove?
Terraform checks the complete configuration after formatting. A successful result means the planned resource references are internally consistent.
The configuration is ready for a deployment preview.
Before you preview the changes, how many new routing resources do you expect Terraform to propose?
- Preview the public routing changes by running this command:
terraform plan
What should the plan contain?
- The plan proposes one internet gateway.
- The plan proposes one public route table.
- The plan proposes one default route.
- The plan proposes two public subnet associations.
You should see five additions. The existing VPC and four subnets should remain in place.
Plan shows unexpected changes?
- Stop before applying the plan.
- Compare network.tf with the full-file reference above.
- Confirm terraform.tfvars still sets environment to dev.
Help me understand why my Terraform plan includes changes beyond the five public routing resources.
This next command changes your AWS network. The project still omits NAT gateways and traffic-generating workloads.
Terraform creates a fresh plan before pausing for approval. Check that it still proposes the same five additions before entering yes.
- Start the public routing deployment by running this command:
- Review the proposed additions when PowerShell pauses for approval.
- Approve the deployment by entering yes.
terraform apply
What does the apply do?
Terraform creates the approved gateway, route table, route, and subnet associations. It also records every new resource in local state.
The deployment completes with five new managed resources. Your public routing path is now live in AWS.
Apply did not complete?
- Read the failed resource address in the PowerShell output.
- Confirm the terraform-learning profile still has permission to manage VPC networking resources.
- Run the plan again after correcting the reported configuration or permission issue.
Help me troubleshoot the failed Terraform apply for my public VPC routing resources.
Before the final check, which destination do you expect the deployed route to cover?
- Inspect the deployed public route by running this command:
terraform state show aws_route.public_internet
What does the route state prove?
- The route state records the deployed IPv4 destination.
- The route state records the managed gateway that receives matching traffic.
You will see destination_cidr_block = "0.0.0.0/0". You will also see a gateway_id for the managed internet gateway.
That is the proof: both public subnets now use a route table with an internet path.
Route missing from state?
- Confirm the apply completed successfully.
- Check that the state address is aws_route.public_internet.
- Run the apply again if the route remains pending in the plan.
Help me troubleshoot why the deployed public route is missing or incomplete in Terraform state.
Your public tier now has a deliberate route through a managed internet gateway. Next, you will make the private tier's isolation explicit and export the network IDs for later projects.
Isolate the Private Tier and Export Its IDs
Your public tier now has a default route through its internet gateway. The private subnets still inherit the VPC's main route table, which hides the isolation decision inside an AWS default.
Terraform can make that isolation explicit with a private route table that contains only AWS's implicit local route. Outputs then give later projects stable identifiers without a console search.
In this step, get ready to:
- Create an explicit private route table.
- Associate each private subnet with that route table.
- Inspect reusable network IDs through Terraform outputs.
Make private routing explicit
A private route table records the routing policy for your private subnet tier. Its implicit local route allows communication inside the VPC without creating a path to the internet.
- Switch back to network.tf in the Visual Studio Code editor from earlier.
- Place your cursor after the closing brace of aws_route_table_association.public_b at the end of the file.
- Add the private route table by pasting this code:
resource "aws_route_table" "private" {
vpc_id = aws_vpc.main.id
tags = {
Name = "${var.project_name}-${var.environment}-private-rt"
Tier = "private"
}
}
What does this resource do?
- The aws_route_table.private resource creates a route table inside aws_vpc.main.
- The route table receives AWS's implicit local route for traffic inside the VPC.
- The tags identify the table as the private tier for the current environment.
- Save network.tf.
- Confirm the configuration remains valid by running this command in PowerShell:
terraform validate
What does this check prove?
Terraform checks the saved configuration for valid syntax and consistent references. This confirms that the private route table can reference the existing VPC.
You should see Terraform confirm that the configuration is valid.
Is the private route table invalid?
Check that aws_vpc.main.id matches the existing VPC reference. Confirm that every opening brace has a matching closing brace.
Help me fix the private route table configuration.
The route table affects a subnet through an explicit association. Each association makes the intended path visible in Terraform state.
- Place your cursor after the aws_route_table.private resource.
- Associate aws_subnet.private_a with the private route table by pasting this code:
resource "aws_route_table_association" "private_a" {
subnet_id = aws_subnet.private_a.id
route_table_id = aws_route_table.private.id
}
How does this association work?
- The subnet_id value selects the private subnet in the first Availability Zone.
- The route_table_id value assigns the new private route table to that subnet.
- Save network.tf.
- Check the first association by running this command:
terraform validate
What is Terraform checking now?
Terraform checks that the first association references resources declared in the same configuration. A successful result proves that the subnet and route table addresses resolve.
You should see Terraform confirm that the updated configuration is valid.
Is the first association invalid?
Compare aws_subnet.private_a.id with the subnet address in vpc.tf. Check that the route table reference ends with aws_route_table.private.id.
Help me fix the first private subnet association.
- Place your cursor after the aws_route_table_association.private_a resource.
- Associate aws_subnet.private_b with the private route table by pasting this code:
resource "aws_route_table_association" "private_b" {
subnet_id = aws_subnet.private_b.id
route_table_id = aws_route_table.private.id
}
What completes the private tier?
- The second association selects the private subnet in the other Availability Zone.
- Both private subnets now declare the same isolated routing policy in code.
- Save network.tf.
- Check both private associations by running this command:
terraform validate
What does the final routing check prove?
Terraform now checks the private route table together with both subnet associations. A successful result confirms that the routing references form a consistent configuration.
You should see Terraform confirm that the configuration is valid.
Is the second association invalid?
Confirm that the resource is named private_b. Check that its subnet reference uses aws_subnet.private_b.id.
Help me fix the second private subnet association.
✔️ Awesome, I've got everything!
Your network.tf now declares explicit routing for both subnet tiers.
ⓧ I'd like to double check the full code
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = {
Name = "${var.project_name}-${var.environment}-igw"
}
}
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
tags = {
Name = "${var.project_name}-${var.environment}-public-rt"
Tier = "public"
}
}
resource "aws_route" "public_internet" {
route_table_id = aws_route_table.public.id
destination_cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
resource "aws_route_table_association" "public_a" {
subnet_id = aws_subnet.public_a.id
route_table_id = aws_route_table.public.id
}
resource "aws_route_table_association" "public_b" {
subnet_id = aws_subnet.public_b.id
route_table_id = aws_route_table.public.id
}
resource "aws_route_table" "private" {
vpc_id = aws_vpc.main.id
tags = {
Name = "${var.project_name}-${var.environment}-private-rt"
Tier = "private"
}
}
resource "aws_route_table_association" "private_a" {
subnet_id = aws_subnet.private_a.id
route_table_id = aws_route_table.private.id
}
resource "aws_route_table_association" "private_b" {
subnet_id = aws_subnet.private_b.id
route_table_id = aws_route_table.private.id
}
How to use this reference
This is the complete network.tf file for this step. Compare its resource names and references with your saved file.
Export reusable network IDs
An output exposes a selected value after Terraform applies the configuration. These outputs create a stable handoff for the storage and compute projects that follow.
- Click the New File icon in the Explorer sidebar.
- Enter outputs.tf as the file name.
- Add the VPC output by pasting this code:
output "vpc_id" {
description = "ID of the VPC foundation"
value = aws_vpc.main.id
}
What does this output expose?
The vpc_id output returns the ID of aws_vpc.main. Later configurations can use this value to target the same network foundation.
- Save outputs.tf.
- Check the VPC output definition by running this command:
terraform validate
What does this validation cover?
Terraform checks that the output references the existing VPC resource. It also checks the new file as part of the complete configuration.
You should see Terraform confirm that the configuration is valid.
Is the VPC output invalid?
Check that the output value is aws_vpc.main.id. Confirm that the output block has one opening brace and one closing brace.
Help me fix the VPC output.
The subnet layout spans two Availability Zones. Exporting those zone names preserves the placement context alongside the resource IDs.
- Place your cursor below the vpc_id output.
- Add the Availability Zone output by pasting this code:
output "availability_zones" {
description = "Availability Zones used by the subnet tiers"
value = [
data.aws_availability_zones.available.names[0],
data.aws_availability_zones.available.names[1]
]
}
What does this list preserve?
The availability_zones output returns the first two standard zones selected by the existing data source. The list order matches the subnet placement used in vpc.tf.
- Save outputs.tf.
- Check the Availability Zone output by running this command:
terraform validate
What does Terraform verify here?
Terraform checks both indexed references against the Availability Zones data source. This catches incorrect data source names before the next apply.
You should see Terraform confirm that the configuration is valid.
Is the zone output invalid?
Check that both values use data.aws_availability_zones.available.names. Confirm that the indexes are 0 and 1.
Help me fix the Availability Zone output.
- Place your cursor below the availability_zones output.
- Add the public subnet IDs by pasting this code:
output "public_subnet_ids" {
description = "IDs of the public subnets"
value = [
aws_subnet.public_a.id,
aws_subnet.public_b.id
]
}
What do these subnet IDs represent?
The public_subnet_ids output returns both publicly routed subnet IDs. Their order follows the first and second Availability Zones.
- Save outputs.tf.
- Check the public subnet output by running this command:
terraform validate
What does this subnet check prove?
Terraform confirms that both public subnet resource addresses exist. It also checks that the list syntax is complete.
You should see Terraform confirm that the configuration is valid.
Is the public subnet output invalid?
Confirm that the list contains aws_subnet.public_a.id and aws_subnet.public_b.id. Check the comma after the first list item.
Help me fix the public subnet output.
- Place your cursor below the public_subnet_ids output.
- Add the private subnet IDs by pasting this code:
output "private_subnet_ids" {
description = "IDs of the private subnets"
value = [
aws_subnet.private_a.id,
aws_subnet.private_b.id
]
}
Why export the private subnet IDs?
The private_subnet_ids output returns both isolated subnet IDs. Later workloads can use this list when they need placement inside the private tier.
- Save outputs.tf.
- Check the private subnet output by running this command:
terraform validate
What does this private subnet check cover?
Terraform checks that both private subnet references resolve. It also verifies the new list as part of the root configuration.
You should see Terraform confirm that the configuration is valid.
Is the private subnet output invalid?
Confirm that the list uses aws_subnet.private_a.id and aws_subnet.private_b.id. Check that both resource names include private.
Help me fix the private subnet output.
Subnet IDs identify placement. Route table IDs identify the routing policy attached to each tier.
- Place your cursor below the private_subnet_ids output.
- Add the public route table ID by pasting this code:
output "public_route_table_id" {
description = "ID of the public route table"
value = aws_route_table.public.id
}
What does this route table ID identify?
The public_route_table_id output returns the table that contains the public default route. This value identifies the routing policy shared by both public subnets.
- Save outputs.tf.
- Check the public route table output by running this command:
terraform validate
What does this routing check prove?
Terraform verifies that aws_route_table.public exists in the configuration. The output can therefore expose its managed ID after apply.
You should see Terraform confirm that the configuration is valid.
Is the public route table output invalid?
Check that the value uses aws_route_table.public.id. Compare the resource name with the public route table in network.tf.
Help me fix the public route table output.
- Place your cursor below the public_route_table_id output.
- Add the shared private route table ID by pasting this code:
output "private_route_table_id" {
description = "ID of the shared private route table"
value = aws_route_table.private.id
}
What completes the network handoff?
The private_route_table_id output returns the table shared by both private subnets. The full output set now describes network placement and routing.
- Save outputs.tf.
- Check the complete output configuration by running this command:
terraform validate
What does the complete check cover?
Terraform checks every output reference against the VPC configuration. A successful result confirms that the reusable values are ready for planning.
You should see Terraform confirm that the complete configuration is valid.
Is the private route table output invalid?
Check that the value is aws_route_table.private.id. Confirm that network.tf contains the matching aws_route_table.private resource.
Help me fix the private route table output.
✔️ Awesome, I've got everything!
Your outputs.tf now exposes the VPC layout and both routing tiers.
ⓧ I'd like to double check the full code
output "vpc_id" {
description = "ID of the VPC foundation"
value = aws_vpc.main.id
}
output "availability_zones" {
description = "Availability Zones used by the subnet tiers"
value = [
data.aws_availability_zones.available.names[0],
data.aws_availability_zones.available.names[1]
]
}
output "public_subnet_ids" {
description = "IDs of the public subnets"
value = [
aws_subnet.public_a.id,
aws_subnet.public_b.id
]
}
output "private_subnet_ids" {
description = "IDs of the private subnets"
value = [
aws_subnet.private_a.id,
aws_subnet.private_b.id
]
}
output "public_route_table_id" {
description = "ID of the public route table"
value = aws_route_table.public.id
}
output "private_route_table_id" {
description = "ID of the shared private route table"
value = aws_route_table.private.id
}
How to use this reference
This is the complete outputs.tf file for this step. Compare every output name and resource reference with your saved file.
Apply and inspect the outputs
The saved configuration describes one private route table with two explicit associations. A plan reveals whether Terraform can add that isolation without replacing the existing network.
- Format the Terraform files by running this command:
terraform fmt
What does formatting do?
Terraform applies its canonical layout to the configuration files. Consistent formatting makes later changes easier to review.
The command may list files that Terraform reformatted. A silent result means the files already use the canonical layout.
- Validate the formatted configuration by running this command:
terraform validate
Why validate again?
This final validation checks the formatted files as one configuration. It confirms that the project is ready for a plan.
You should see Terraform confirm that the configuration is valid.
Does validation fail now?
Use the file and line details in Terraform's message to find the affected block. Compare that block with the full-file references above.
Help me diagnose the final validation problem.
Before you preview the change, which new routing resources do you expect Terraform to propose?
- Preview the infrastructure changes by running this command:
terraform plan
What does this plan represent?
The plan compares the saved configuration with the existing Terraform state. It shows the proposed private route table and both private subnet associations before AWS changes.
You should see the private route table and two associations proposed for creation. The existing VPC and subnet resources should remain in place.
Does the plan show unexpected replacements?
Stop before applying if Terraform proposes replacing the VPC or any subnet. Compare your existing resource names and CIDR values with the files from the previous step.
Help me review unexpected Terraform replacements.
- Apply the reviewed configuration by running this command:
terraform apply
What happens during this apply?
Terraform creates a fresh plan. It pauses for approval before changing AWS.
- Enter yes when Terraform asks for approval.
Terraform should report a completed apply after creating the private route table and both associations.
Did the apply stop?
Read the resource address in Terraform's message to identify the failed route table or association. Confirm that the authenticated profile still has permission to manage VPC networking resources.
Help me troubleshoot the failed Terraform apply.
Before you run the final check, which identifiers should now be reusable without opening the AWS console?
- Display the reusable network values by running this command:
terraform output
What should the outputs contain?
- The vpc_id output identifies the managed VPC.
- The availability_zones output lists the two selected zones.
- The public_subnet_ids output lists the two publicly routed subnets.
- The private_subnet_ids output lists the two isolated subnets.
- The public_route_table_id output identifies the public routing policy.
- The private_route_table_id output identifies the shared private routing policy.
You should see all six output groups with values from the applied network. The subnet outputs should each contain two IDs.
Are outputs missing?
Confirm that outputs.tf is saved inside terraform-vpc-foundation. Run the apply again if the output definitions were added after the last successful apply.
Help me troubleshoot missing Terraform outputs.
Strong work. Your private tier now has explicit isolation plus reusable identifiers for the projects that follow. Next, you'll test Terraform's lifecycle with a controlled tag change.
Prove the Terraform Lifecycle
Your two-AZ network now has explicit public and private routing. Its outputs are ready for later storage and compute projects.
The final proof is whether Terraform can distinguish stable infrastructure from an intentional configuration change. Infrastructure as code becomes trustworthy when an unchanged configuration produces no work.
A small environment edit should update tags in place. The network itself should stay intact.
In this step, get ready to:
- Confirm that an unchanged configuration requires no work.
- Preview only the tag changes caused by an environment update.
- Return Terraform to a clean state after applying the update.
Confirm an unchanged plan
An unchanged Terraform plan is an idempotency check. It proves that repeatedly evaluating the same configuration does not disturb stable infrastructure.
Before you run this check, do you expect Terraform to propose any work?
- Return to the PowerShell terminal from earlier.
- Check the unchanged configuration by running this command:
terraform plan
What Does This Plan Prove?
- The command refreshes managed resource data through the AWS provider.
- Terraform state records the resource bindings Terraform already manages.
- The plan compares that state with the saved configuration.
PowerShell reports that no changes are required. This confirms that the deployed network matches your configuration.
Seeing Unexpected Changes?
- Review each proposed resource change to identify the differing argument.
- Compare that argument with the matching Terraform file in Visual Studio Code.
Ask for help diagnosing unexpected changes in the first Terraform plan.
Update the environment value
The environment variable feeds the default Environment tag. It also appears in the managed resource names.
Changing this single value tests whether Terraform can trace a targeted update without rebuilding the network.
- Switch back to Visual Studio Code.
- Select terraform.tfvars in the Explorer sidebar.
- Find environment = "dev".
- Replace it with environment = "test".
- Save terraform.tfvars.
✔️ Awesome, I've got everything!
Your saved terraform.tfvars now targets the test environment.
ⓧ I'd like to double check the full code
aws_region = "us-east-1"
aws_profile = "terraform-learning"
project_name = "terraform-vpc-foundation"
environment = "test"
What Changed?
Only the environment assignment changes from dev to test. The Region, profile, and project name remain unchanged.
Before you preview the edit, which resource properties do you expect Terraform to change?
- Return to the PowerShell terminal from earlier.
- Preview the effect of the saved value by running this command:
terraform plan
What Does This Plan Show?
- The command previews the difference without applying it.
- The environment value flows into the default Environment tags.
- The value also flows into each managed Name tag.
You should see existing tagged resources updated in place. You should not see replacement of the VPC, subnets, internet gateway, routes, or route tables.
Does the Plan Propose Replacements?
- Confirm that terraform.tfvars contains environment = "test".
- Restore any unrelated file edits before continuing.
- Run the plan again after saving the corrected file.
Ask for help understanding why the environment edit proposes resource replacement.
Apply and verify the clean state
The apply stage turns the preview into actual AWS tag changes. Terraform shows a fresh plan before pausing at a confirmation prompt.
- Apply the environment tag update by running this command:
terraform apply
What Does This Command Do?
- Terraform creates a fresh plan from the saved configuration.
- Your approval allows Terraform to send the proposed tag updates to AWS.
- The existing network resource IDs remain attached to the same Terraform addresses.
- Review the proposed tag updates in PowerShell.
- Approve the plan at Terraform's confirmation prompt.
You should see Terraform finish the approved updates without creating a new network. The managed Name and Environment tags now use test.
Did the Apply Fail?
- Confirm that the PowerShell terminal still uses the authenticated profile from earlier.
- Check the failed resource entry for a permissions or connectivity issue.
- Run the apply command again after correcting the reported cause.
Ask for help diagnosing the failed Terraform apply.
Before you run the final check, do you expect Terraform to find another change?
- Verify the applied configuration by running this command:
terraform plan
What Does the Final Plan Confirm?
- The command repeats the configuration-to-state comparison after the apply.
- A clean result confirms that the saved configuration matches Terraform state.
- It also confirms that the remote AWS tags match the requested test environment.
PowerShell reports that no changes are required. You have now proved the full Terraform lifecycle from a clean plan through a targeted update and back to a clean plan.
Does the Final Plan Still Show Changes?
- Confirm that the previous apply completed successfully.
- Confirm that terraform.tfvars is saved with the test value.
- Apply any remaining intended tag updates before repeating the final check.
Ask for help resolving changes that remain after the Terraform apply.
Secret mission
Give Each Private Availability Zone Its Own Route Table
Your private subnets currently share one route table. Split that shared design into one private route table per Availability Zone to prepare the network for future zonal routing decisions.
Clean Up Your Resources
Clean Up Your Resources
Choose how much of your network foundation you want to keep. The AWS resources created here are expected to cost $0 unless you add resources or generate data transfer.
Resources you used:
- One Amazon VPC in us-east-1.
- Four subnets spread across two Availability Zones.
- One internet gateway attached to the VPC.
- One public route table with a 0.0.0.0/0 route through the internet gateway.
- Two private route tables with one private subnet association each.
- The local terraform-vpc-foundation folder containing your Terraform configuration and terraform.tfstate.
Keep everything running
No action is needed. Choose this if you plan to continue with the storage or compute projects in this series.
- Retain the AWS network for the follow-on Amazon S3 project.
- Retain the AWS network for the follow-on Amazon EC2 project.
- Keep the terraform-vpc-foundation folder with its local state.
- Continue using the terraform-learning profile when you return.
Pause - I'll come back to this later
Pausing this series removes the AWS network. Your local configuration remains ready to recreate it later.
- Preview the AWS deletions from the open terraform-vpc-foundation PowerShell session by running:
terraform plan -destroy
What does this preview show?
The terraform plan -destroy command shows what Terraform intends to remove without changing AWS. The preview lets you check every deletion before approving it.
Removing the network feels like a big step. Your .tf files remain available to recreate the same foundation later.
- Review the destruction preview for the VPC.
- Review the destruction preview for every dependent networking resource.
- Begin the cleanup by running:
terraform destroy
What does this command do?
Terraform reads local state to deprovision each managed AWS object in dependency order. It waits for your approval before changing the network.
- Enter yes when Terraform requests approval.
Terraform confirms that the managed AWS resources were destroyed. Your local configuration is ready for the next session.
Did the cleanup stop?
An unavailable terraform-learning profile can prevent Terraform from reaching AWS. Return to the identity verification in Step 1 before retrying the cleanup.
Help me diagnose why Terraform could not destroy my AWS network resources.
Delete - I don't want to use this again
Remove all project resources and start fresh. Choose this if you no longer need the AWS network or its local state history.
Delete the AWS resources first
Deleting the foundation is a significant action. Terraform removes the AWS resources in dependency order before you delete the local state.
- Start the AWS cleanup from the open terraform-vpc-foundation PowerShell session by running:
terraform destroy
What does this command remove?
Terraform uses terraform.tfstate to deprovision every managed AWS object. The local project folder remains after the cloud cleanup finishes.
- Enter yes when Terraform requests approval.
That is the cloud cleanup complete. Terraform confirms that the VPC and its dependent networking resources were destroyed.
Could Terraform not finish?
Keep the project folder until Terraform completes the destruction. Deleting local state first removes the information Terraform needs to manage the remaining AWS resources.
Help me safely finish a failed Terraform cleanup.
Delete the local project folder:
- Close Visual Studio Code after Terraform confirms the AWS cleanup.
- Close the Windows PowerShell window using terraform-vpc-foundation.
- Delete the terraform-vpc-foundation folder with your Windows file manager.
- Confirm the terraform-vpc-foundation folder is no longer listed in its parent location.
Nice Work!
Nice Work!
Excellent work on completing your reusable AWS network foundation. Terraform now manages a VPC across two Availability Zones with explicit public and private routing.
You've learned how to:
- Build a two-AZ VPC with separate public and private subnet tiers.
- Prove the Terraform lifecycle through planned creation, in-place tag changes, and a clean no-change result.
- Export reusable network IDs for the later Amazon S3 and Amazon EC2 projects.
- Secret Mission: Replace the shared private route table with one private route table per Availability Zone. Export two distinct private route table IDs.
Project 1 of your three-project Terraform series is complete. Your network foundation is ready to support the storage and compute projects that follow.
Ready to quiz yourself?