VPC with Public and Private Subnets
Provision a full AWS VPC networking stack using Terraform code.
Introduction
30 Second Summary
Imagine setting up a network by clicking through dozens of screens, configuring each piece one by one. Now imagine doing that every time you need a new environment. Cloud engineers face this exact problem when they build infrastructure manually through the AWS Console.
In this project, you will write Terraform code that defines an entire VPC networking setup and provisions it with a single command.
What You'll Build
A full VPC networking stack defined entirely in HCL (HashiCorp Configuration Language) files that you can create, inspect, and destroy from the terminal.
By the end of this project, you'll have:
- A complete Terraform configuration that declares a VPC, subnets, an internet gateway, route tables, a security group, and network ACLs.
- Hands-on experience running terraform plan and terraform apply to provision real AWS infrastructure from code.
- A reusable .tf file that can recreate or destroy your entire network in seconds.
- Secret Mission: Add variables and outputs to make your configuration flexible and informative.
Are there any prerequisites?
You need an AWS account with an IAM admin user configured. You also need the AWS CLI installed and configured with access keys on your machine.
Install and Configure Terraform
Right now, the only way to build a VPC on AWS is to click through dozens of console screens. If you want to recreate the same setup tomorrow, you have to remember every setting and repeat every click.
Terraform solves this by letting you define your infrastructure in code files. In this step, you will install the Terraform CLI, confirm your AWS credentials are working, and create your project folder with the initial configuration file.
In this step, get ready to:
- Install Terraform and verify the installation.
- Confirm your AWS CLI credentials are working.
- Create your project directory and initial provider configuration.
Install Terraform and verify your installation
Terraform is a command-line tool that reads your configuration files and turns them into real cloud resources. You need it installed on your machine before you can write any infrastructure code.
- Open your terminal by pressing Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Terminal (macOS) or PowerShell (Windows) and press Enter to open it.
- Check whether Terraform is already installed by running this command:
terraform version
✔️ I see version 1.15.0 or higher
Your Terraform installation is ready. Skip ahead to the next substep where you verify your AWS CLI credentials.
ⓧ I see an older version
You have Terraform installed, but the version is older than 1.15.0. This project requires 1.15.0 or higher.
macOS
- Upgrade Terraform by running these commands:
brew tap hashicorp/tap
brew upgrade hashicorp/tap/terraform
- Verify the upgrade by running:
terraform version
You should now see 1.15.0 or higher in the output.
Windows
- Upgrade Terraform by running this command in PowerShell (as Administrator):
choco install terraform
- Verify the upgrade by running:
terraform version
You should now see 1.15.0 or higher in the output.
ⓧ Command not found
Terraform is not installed on your machine yet. Follow the instructions below for your operating system.
macOS
- Install Terraform via Homebrew by running these commands:
brew tap hashicorp/tap
brew install hashicorp/tap/terraform
What do these commands do?
The first command adds HashiCorp's official tap (package repository) to Homebrew. The second command installs the Terraform CLI from that tap.
- Verify the installation by running:
terraform version
You should see a version number like Terraform v1.15.5 in the output.
Windows
- Install Terraform via Chocolatey by running this command in PowerShell (as Administrator):
choco install terraform
What does this command do?
Chocolatey is a package manager for Windows. This command downloads and installs the latest Terraform CLI binary to your system.
- Close and reopen your PowerShell window, then verify the installation by running:
terraform version
You should see a version number like Terraform v1.15.5 in the output.
No Chocolatey?
If you do not have Chocolatey installed, you can download the Terraform binary directly from the official install page. Extract the zip and add the folder to your system PATH.
Still seeing an error?
If brew itself is not found, you need to install Homebrew first from brew.sh. If choco is not found on Windows, install Chocolatey from chocolatey.org/install.
help me install Terraform on my machine
Verify your AWS CLI credentials
Terraform needs access to your AWS account to create resources. The AWS CLI stores credentials that Terraform will automatically pick up, so you need to confirm those credentials are valid.
- Verify your AWS credentials are configured and working by running this command:
aws sts get-caller-identity
You should see JSON output containing your Account, UserId, and Arn fields.
What does this command do?
This command asks AWS "who am I?" using the credentials stored in your AWS CLI configuration. If it returns your account information, Terraform will be able to use those same credentials to create and manage resources.
Seeing "Unable to locate credentials"?
This means your AWS CLI is not configured with access keys. Run aws configure and enter your IAM admin user's Access Key ID and Secret Access Key. Set the default region to us-east-1 and the output format to json.
help me configure my AWS CLI credentials
Create your project directory and provider configuration
Every Terraform project lives in its own directory. Terraform reads all .tf files in the current directory and combines them into a single configuration.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir nextwork-vpc-terraform
cd nextwork-vpc-terraform
- Confirm the folder was created by running:
pwd
You should see a path ending in nextwork-vpc-terraform.
- Open your code editor by pressing Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type VS Code (or Cursor) and press Enter to open it.
- Click File in the top menu bar.
- Click Open Folder.
- Navigate to your Desktop, select the nextwork-vpc-terraform folder, and click Open.
- Create a new file by clicking File then New File.
- Name the file main.tf and press Enter to save it.
Now add the provider configuration. This tells Terraform which cloud provider to use and which version of the provider plugin to download.
- Paste the following code into main.tf:
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.47"
}
}
required_version = ">= 1.15.0"
}
provider "aws" {
region = "us-east-1"
}
What does this code do?
The terraform block sets project-wide settings. Inside it, required_providers declares that you need the AWS provider from HashiCorp's registry. The ~> 6.47 constraint allows any 6.47.x patch version but not 6.48 or higher.
The required_version line ensures anyone running this code has Terraform 1.15.0 or newer. The provider block configures the AWS provider to deploy resources in the us-east-1 region.
- Save main.tf by pressing Cmd+S (macOS) or Ctrl+S (Windows).
File not saving as .tf?
Make sure the filename is exactly main.tf with no extra extension like .txt. If your editor added an extension, rename the file by right-clicking it in the Explorer sidebar and selecting Rename.
help me create a .tf file in my editor
✔️ Awesome, I've got everything!
Great. Double check you have saved main.tf in the nextwork-vpc-terraform folder on your Desktop.
ⓧ I'd like to double check the full code
Here is the complete main.tf file at this point:
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.47"
}
}
required_version = ">= 1.15.0"
}
provider "aws" {
region = "us-east-1"
}
Your environment is fully set up. Next up, you will define the VPC, subnets, and internet gateway that form the backbone of your network.
Define the VPC, Subnets, and Internet Gateway
In the last step, you installed Terraform and set up your project with the AWS provider configuration. Now you have a foundation to start defining real cloud infrastructure.
In this step, you'll write the core networking resources for your VPC. You'll define a virtual private cloud, attach an internet gateway to it, and carve out two subnets with different access levels.
In this step, get ready to:
- Define the VPC and internet gateway in Terraform code.
- Add public and private subnets with different access levels.
- Initialize Terraform and preview the infrastructure plan.
Write the VPC and internet gateway resources
A VPC is an isolated virtual network inside AWS. An internet gateway is the bridge that lets resources inside your VPC communicate with the public internet.
- In main.tf, add the following VPC and internet gateway resources below the provider "aws" block:
# VPC
resource "aws_vpc" "nextwork" {
cidr_block = "10.0.0.0/16"
tags = {
Name = "NextWork VPC"
}
}
# Internet Gateway
resource "aws_internet_gateway" "nextwork" {
vpc_id = aws_vpc.nextwork.id
tags = {
Name = "NextWork IG"
}
}
What does this code do?
The aws_vpc resource creates a virtual private cloud with a CIDR block of 10.0.0.0/16. This gives your network 65,536 private IP addresses to work with.
The aws_internet_gateway resource attaches to the VPC using aws_vpc.nextwork.id. This is how Terraform creates dependencies between resources. When you reference one resource inside another, Terraform knows which to create first.
- Save main.tf.
You won't see a change yet. The next chunk adds subnets, and then you'll initialize Terraform to validate everything.
Seeing red squiggles in your editor?
If your editor highlights the HCL syntax as errors, make sure you saved the file with the .tf extension. Some editors need the HashiCorp Terraform extension installed to recognize HCL syntax.
help me fix syntax highlighting for Terraform files
Write the public and private subnet resources
Subnets divide your VPC into smaller network segments. A public subnet auto-assigns public IPs and will later get a route to the internet. A private subnet keeps resources completely isolated.
- In main.tf, add the following subnet resources below the internet gateway resource:
# Public Subnet
resource "aws_subnet" "public" {
vpc_id = aws_vpc.nextwork.id
cidr_block = "10.0.0.0/24"
map_public_ip_on_launch = true
availability_zone = "us-east-1a"
tags = {
Name = "NextWork Public Subnet"
}
}
# Private Subnet
resource "aws_subnet" "private" {
vpc_id = aws_vpc.nextwork.id
cidr_block = "10.0.1.0/24"
availability_zone = "us-east-1b"
tags = {
Name = "NextWork Private Subnet"
}
}
What does this code do?
The public subnet uses map_public_ip_on_launch = true, which means any instance launched into this subnet automatically gets a public IP address. The private subnet omits this setting, so its instances stay invisible to the outside world.
Each subnet lives in a different availability zone (us-east-1a and us-east-1b). Spreading subnets across zones is a best practice for fault tolerance.
- Save main.tf.
You won't see a change yet. In the next substep, you'll initialize Terraform and run a plan to confirm everything is valid.
Getting a syntax error when you save?
Check that every opening { has a matching closing }. HCL blocks nest (the tags block sits inside the resource block), so it's easy to miss one.
help me find the syntax error in my Terraform file
✔️ Awesome, I've got everything!
Great. Double check you've saved main.tf before moving on.
ⓧ I'd like to double check the full code
Here is what your full main.tf should look like at this point:
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.47"
}
}
required_version = ">= 1.15.0"
}
provider "aws" {
region = "us-east-1"
}
# VPC
resource "aws_vpc" "nextwork" {
cidr_block = "10.0.0.0/16"
tags = {
Name = "NextWork VPC"
}
}
# Internet Gateway
resource "aws_internet_gateway" "nextwork" {
vpc_id = aws_vpc.nextwork.id
tags = {
Name = "NextWork IG"
}
}
# Public Subnet
resource "aws_subnet" "public" {
vpc_id = aws_vpc.nextwork.id
cidr_block = "10.0.0.0/24"
map_public_ip_on_launch = true
availability_zone = "us-east-1a"
tags = {
Name = "NextWork Public Subnet"
}
}
# Private Subnet
resource "aws_subnet" "private" {
vpc_id = aws_vpc.nextwork.id
cidr_block = "10.0.1.0/24"
availability_zone = "us-east-1b"
tags = {
Name = "NextWork Private Subnet"
}
}
Initialize Terraform and preview the plan
Before Terraform can do anything with your configuration, it needs to download the AWS provider plugin you declared. After that, you can run a plan to preview exactly what resources Terraform would create.
- Initialize your Terraform working directory by running this command:
terraform init
- You should see Terraform has been successfully initialized! along with a note about the AWS provider being installed.
What does terraform init do?
This command reads your required_providers block and downloads the matching provider plugin (in this case, hashicorp/aws) into a hidden .terraform/ directory. It also creates a .terraform.lock.hcl file to lock the exact version it downloaded.
Seeing an error during init?
Make sure you are running the command from inside the nextwork-vpc-terraform directory that contains your main.tf file. If you see a network-related error, check your internet connection.
help me fix terraform init errors
- Preview the resources Terraform will create by running:
terraform plan
- You should see Plan: 4 to add, 0 to change, 0 to destroy. at the bottom of the output.
- Scroll up to review the four resources listed: the VPC, internet gateway, public subnet, and private subnet.
What does terraform plan do?
The terraform plan command reads your .tf files and compares the desired state to the current state (which is empty right now). It outputs a detailed list of every resource it would create, change, or destroy.
Nothing is actually created at this point. Think of it as a dry run that validates your configuration and gives you a chance to review before committing.
Plan shows errors instead of resources?
A common cause is a typo in a resource reference. Check that your subnet resources reference aws_vpc.nextwork.id and not a misspelled name. Also confirm your AWS credentials are still valid by running aws sts get-caller-identity in a separate terminal.
help me fix my terraform plan errors
Your VPC, subnets, and internet gateway are defined and validated. Next up, you'll add route tables to control how traffic flows between these subnets and the internet.
Add Route Tables and Associations
Your VPC, subnets, and internet gateway are defined. But right now, none of your subnets know how to route traffic.
Route tables solve this. They act as traffic rules telling each subnet where to send outgoing network requests. In this step, you'll create a public route table that sends internet-bound traffic through the gateway, and a private route table that keeps traffic internal.
In this step, get ready to:
- Write the public route table with a route to the internet gateway.
- Write the private route table with no internet route.
- Run terraform plan to preview the new resources.
Write the public route table and association
A public subnet needs a route that sends all non-local traffic (0.0.0.0/0) to the internet gateway. Without this route, instances in the public subnet cannot reach the internet even though the gateway exists.
- In main.tf, add the following code below your Private Subnet resource block:
# Public Route Table
resource "aws_route_table" "public" {
vpc_id = aws_vpc.nextwork.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.nextwork.id
}
tags = {
Name = "NextWork Public Route Table"
}
}
# Public Route Table Association
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?
The aws_route_table resource creates a route table attached to your VPC. The inline route block defines a rule: any traffic heading to 0.0.0.0/0 (anywhere on the internet) gets sent to the internet gateway.
The aws_route_table_association resource links this route table to the public subnet. Without the association, the subnet would use the VPC's default (main) route table instead.
- Save main.tf.
- You won't see a change yet. The private route table comes next, then you'll run terraform plan to preview everything together.
Seeing a syntax error on save?
Check that the route { } block is nested inside the aws_route_table resource, not outside it. Every opening brace needs a matching closing brace.
Help me fix this syntax error
Write the private route table and association
The private subnet intentionally has no route to the internet. Its route table only contains the implicit local route that Terraform and AWS add automatically, keeping traffic confined within the VPC.
- In main.tf, add the following code below the Public Route Table Association resource:
# Private Route Table
resource "aws_route_table" "private" {
vpc_id = aws_vpc.nextwork.id
tags = {
Name = "NextWork Private Route Table"
}
}
# Private Route Table Association
resource "aws_route_table_association" "private" {
subnet_id = aws_subnet.private.id
route_table_id = aws_route_table.private.id
}
What does this code do?
This route table has no route block at all. That means it only contains the automatic local route (10.0.0.0/16 within the VPC). Traffic destined for the internet has no path out.
The association links it to the private subnet, overriding the VPC's default route table. This is the networking foundation that keeps private resources isolated.
- Save main.tf.
- You won't see a change yet. In the next substep, you'll run terraform plan to confirm everything looks correct.
Not sure where to paste?
The private route table code goes directly after the closing brace of aws_route_table_association "public". Use the double-check tab below to compare your full file.
Help me check my code placement
✔️ Awesome, I've got everything!
Great. Double check you've saved main.tf before running the plan in the next substep.
ⓧ I'd like to double check the full code
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.47"
}
}
required_version = ">= 1.15.0"
}
provider "aws" {
region = "us-east-1"
}
# VPC
resource "aws_vpc" "nextwork" {
cidr_block = "10.0.0.0/16"
tags = {
Name = "NextWork VPC"
}
}
# Internet Gateway
resource "aws_internet_gateway" "nextwork" {
vpc_id = aws_vpc.nextwork.id
tags = {
Name = "NextWork IG"
}
}
# Public Subnet
resource "aws_subnet" "public" {
vpc_id = aws_vpc.nextwork.id
cidr_block = "10.0.0.0/24"
map_public_ip_on_launch = true
availability_zone = "us-east-1a"
tags = {
Name = "NextWork Public Subnet"
}
}
# Private Subnet
resource "aws_subnet" "private" {
vpc_id = aws_vpc.nextwork.id
cidr_block = "10.0.1.0/24"
availability_zone = "us-east-1b"
tags = {
Name = "NextWork Private Subnet"
}
}
# Public Route Table
resource "aws_route_table" "public" {
vpc_id = aws_vpc.nextwork.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.nextwork.id
}
tags = {
Name = "NextWork Public Route Table"
}
}
# Public Route Table Association
resource "aws_route_table_association" "public" {
subnet_id = aws_subnet.public.id
route_table_id = aws_route_table.public.id
}
# Private Route Table
resource "aws_route_table" "private" {
vpc_id = aws_vpc.nextwork.id
tags = {
Name = "NextWork Private Route Table"
}
}
# Private Route Table Association
resource "aws_route_table_association" "private" {
subnet_id = aws_subnet.private.id
route_table_id = aws_route_table.private.id
}
Run terraform plan to preview the new resources
With both route tables and their associations in place, your configuration now describes 8 total resources. Time to verify the execution plan.
- Preview what Terraform will create by running this command:
terraform plan
- Look at the summary line at the bottom of the output. You should see Plan: 8 to add, 0 to change, 0 to destroy.
What are the 8 resources?
- aws_vpc.nextwork (the VPC itself).
- aws_internet_gateway.nextwork (the gateway for internet access).
- aws_subnet.public and aws_subnet.private (your two subnets).
- aws_route_table.public and aws_route_table.private (the route tables you just added).
- aws_route_table_association.public and aws_route_table_association.private (linking route tables to subnets).
Seeing fewer than 8 resources?
Make sure you saved main.tf before running the plan. Terraform reads the file from disk, so unsaved changes won't appear.
If you see an error about invalid references, check that your resource names match exactly: aws_route_table.public, aws_route_table.private, aws_route_table_association.public, and aws_route_table_association.private.
Help me debug my terraform plan
Your route tables are ready to go. Next up, you'll add a security group and network ACLs to control which traffic is actually allowed through.
Add Security Group and Network ACLs
Your route tables are directing traffic along the right paths. Now it's time to add the security layer that controls what traffic is actually allowed to reach your resources.
In this step, you'll define a Security Group for HTTP traffic and Network ACLs that control subnet-level access. Then you'll run terraform apply for the first time to provision everything to AWS.
In this step, get ready to:
- Write an aws_security_group allowing inbound HTTP traffic.
- Write public and private aws_network_acl resources.
- Run terraform apply to provision all resources to AWS.
Write the security group resource
A security group acts as a virtual firewall for resources inside your VPC. You'll create one that allows inbound HTTP traffic on port 80 from any IP address.
- In main.tf, add the following code below the # Private Route Table Association resource block:
# Security Group
resource "aws_security_group" "nextwork" {
name = "NextWork Security Group"
description = "A Security Group for the NextWork VPC"
vpc_id = aws_vpc.nextwork.id
ingress {
description = "Allow HTTP from anywhere"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "NextWork Security Group"
}
}
What does this code do?
The ingress block defines an inbound rule. from_port and to_port are both set to 80, meaning only HTTP traffic is allowed in.
The cidr_blocks = ["0.0.0.0/0"] means any IP address on the internet can send HTTP requests to resources using this security group.
- Save main.tf.
Write the network ACL resources
Network ACLs operate at the subnet level, controlling traffic before it even reaches a security group. You'll create a public ACL that allows all traffic and a private ACL that denies everything by default.
- In main.tf, add the following code below the security group resource you just wrote:
# Public Network ACL
resource "aws_network_acl" "public" {
vpc_id = aws_vpc.nextwork.id
subnet_ids = [aws_subnet.public.id]
ingress {
protocol = "-1"
rule_no = 100
action = "allow"
cidr_block = "0.0.0.0/0"
from_port = 0
to_port = 0
}
egress {
protocol = "-1"
rule_no = 100
action = "allow"
cidr_block = "0.0.0.0/0"
from_port = 0
to_port = 0
}
tags = {
Name = "NextWork Public NACL"
}
}
# Private Network ACL
resource "aws_network_acl" "private" {
vpc_id = aws_vpc.nextwork.id
subnet_ids = [aws_subnet.private.id]
tags = {
Name = "NextWork Private NACL"
}
}
What does this code do?
The public NACL uses protocol = "-1" which means "all protocols". Rule 100 allows all inbound and outbound traffic for the public subnet, since the security group handles fine-grained filtering.
The private NACL has no ingress or egress blocks at all. Custom NACLs deny all traffic by default, so leaving the rules empty means nothing can get in or out of the private subnet.
Why two layers of security?
Security groups are stateful. If you allow inbound traffic, the response is automatically allowed out. NACLs are stateless. You must explicitly allow both inbound and outbound traffic for a connection to work.
Using both gives you defense in depth. The NACL blocks unwanted traffic at the subnet boundary, and the security group filters at the resource level.
- Save main.tf.
Seeing a syntax error?
Make sure each resource block has matching opening and closing braces. The NACL resources have nested ingress and egress blocks that also need their own braces.
help me find the syntax error in my Terraform Network ACL code
✔️ Awesome, I've got everything!
Great. Double check you've saved your file before moving on.
ⓧ I'd like to double check the full code
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.47"
}
}
required_version = ">= 1.15.0"
}
provider "aws" {
region = "us-east-1"
}
# VPC
resource "aws_vpc" "nextwork" {
cidr_block = "10.0.0.0/16"
tags = {
Name = "NextWork VPC"
}
}
# Internet Gateway
resource "aws_internet_gateway" "nextwork" {
vpc_id = aws_vpc.nextwork.id
tags = {
Name = "NextWork IG"
}
}
# Public Subnet
resource "aws_subnet" "public" {
vpc_id = aws_vpc.nextwork.id
cidr_block = "10.0.0.0/24"
map_public_ip_on_launch = true
availability_zone = "us-east-1a"
tags = {
Name = "NextWork Public Subnet"
}
}
# Private Subnet
resource "aws_subnet" "private" {
vpc_id = aws_vpc.nextwork.id
cidr_block = "10.0.1.0/24"
availability_zone = "us-east-1b"
tags = {
Name = "NextWork Private Subnet"
}
}
# Public Route Table
resource "aws_route_table" "public" {
vpc_id = aws_vpc.nextwork.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.nextwork.id
}
tags = {
Name = "NextWork Public Route Table"
}
}
# Public Route Table Association
resource "aws_route_table_association" "public" {
subnet_id = aws_subnet.public.id
route_table_id = aws_route_table.public.id
}
# Private Route Table
resource "aws_route_table" "private" {
vpc_id = aws_vpc.nextwork.id
tags = {
Name = "NextWork Private Route Table"
}
}
# Private Route Table Association
resource "aws_route_table_association" "private" {
subnet_id = aws_subnet.private.id
route_table_id = aws_route_table.private.id
}
# Security Group
resource "aws_security_group" "nextwork" {
name = "NextWork Security Group"
description = "A Security Group for the NextWork VPC"
vpc_id = aws_vpc.nextwork.id
ingress {
description = "Allow HTTP from anywhere"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "NextWork Security Group"
}
}
# Public Network ACL
resource "aws_network_acl" "public" {
vpc_id = aws_vpc.nextwork.id
subnet_ids = [aws_subnet.public.id]
ingress {
protocol = "-1"
rule_no = 100
action = "allow"
cidr_block = "0.0.0.0/0"
from_port = 0
to_port = 0
}
egress {
protocol = "-1"
rule_no = 100
action = "allow"
cidr_block = "0.0.0.0/0"
from_port = 0
to_port = 0
}
tags = {
Name = "NextWork Public NACL"
}
}
# Private Network ACL
resource "aws_network_acl" "private" {
vpc_id = aws_vpc.nextwork.id
subnet_ids = [aws_subnet.private.id]
tags = {
Name = "NextWork Private NACL"
}
}
Run terraform apply to provision all resources
Until now, your infrastructure has only existed in code. Running terraform apply tells Terraform to actually create every resource in your AWS account.
- Provision all your resources to AWS by running this command:
terraform apply
What happens when you run this?
Terraform shows you an execution plan (similar to terraform plan) and then asks you to confirm. It will display a prompt asking you to type yes to proceed.
- Review the plan that appears. You should see 11 resources to add.
- Type yes and press **Enter** to confirm.
Terraform creates each resource one by one. When it finishes, you should see a message saying Apply complete! Resources: 11 added, 0 changed, 0 destroyed.
Apply failed with an error?
Check that your AWS CLI credentials are still valid by running aws sts get-caller-identity in a separate terminal. If that fails, your credentials may have expired.
Also check that you have no typos in your resource references. Terraform error messages will point to the exact line number with the problem.
help me fix my terraform apply error
- Open the AWS Console in your browser.
- Navigate to the **VPC** service by searching VPC in the top search bar.
- Click **Your VPCs** in the left sidebar. You should see NextWork VPC listed.
- Click **Subnets** in the left sidebar. You should see NextWork Public Subnet and NextWork Private Subnet listed.
- Click **Security groups** in the left sidebar. You should see NextWork Security Group listed.
- Click **Network ACLs** in the left sidebar. You should see NextWork Public NACL and NextWork Private NACL listed.
Every resource you defined in code now exists in your AWS account. Next up, you'll tear it all down with a single command to see the full power of Infrastructure as Code.
Tear Down Your Infrastructure
In the last step, you provisioned 11 AWS resources with a single terraform apply command. That is one half of the Infrastructure as Code lifecycle.
The other half is cleanup. Manually deleting a VPC, two subnets, two route tables, two associations, an internet gateway, a security group, and two network ACLs in the AWS Console would take dozens of clicks across multiple pages. Terraform handles it in one command.
In this step, get ready to:
- Run terraform destroy to remove all 11 AWS resources.
- Verify in the AWS Console that every resource is gone.
- See how one command replaced many manual console clicks.
Run terraform destroy
Just like terraform apply created your infrastructure, terraform destroy tears it all down. It reads your state file to find every resource it manages, then deletes them in the correct dependency order.
- Remove all 11 AWS resources by running this command:
terraform destroy
Terraform displays a destroy plan listing all 11 resources it will remove. It then asks for confirmation before making any changes.
- Type yes and press Enter to confirm the destruction.
You should see each resource being destroyed one by one, ending with a confirmation message stating that 11 resources were destroyed.
What does this command do?
The terraform destroy command is the inverse of terraform apply. It terminates all resources tracked in your Terraform state file.
Terraform uses a dependency graph to determine the correct destruction order. For example, it removes route table associations before route tables, and subnets before the VPC itself.
Seeing an error during destroy?
Make sure you are running the command from inside the nextwork-vpc-terraform directory where your main.tf and .terraform folder live.
If you see a credentials error, verify your AWS CLI is still configured by running aws sts get-caller-identity.
Help me fix a terraform destroy error
Verify resources are deleted in the AWS Console
The terminal confirmation is reassuring, but checking the AWS Console lets you see with your own eyes that everything is truly gone.
- Open the AWS Console in your browser.
- Type VPC in the search bar at the top and select the VPC service.
- Click Your VPCs in the left sidebar.
- Confirm that NextWork VPC no longer appears in the list.
- Click Subnets in the left sidebar and confirm that NextWork Public Subnet and NextWork Private Subnet are gone.
What should I see?
Your VPC list should only show the default VPC that AWS provides in every region. All custom resources you created (subnets, route tables, internet gateway, security group, and NACLs) have been removed.
Reflect on one-command cleanup
Think about what just happened. One command removed 11 interconnected resources in the correct order, with no manual clicking required.
Why is this so powerful?
Deleting a VPC manually in the console requires removing dependencies in the right sequence. You would need to delete NACLs, then disassociate and delete route tables, remove the security group, detach and delete the internet gateway, delete subnets, and finally delete the VPC itself.
Terraform tracks all of these dependencies in its state file. It knows the correct order automatically, so you can spin up and tear down entire environments in seconds.
Your infrastructure is cleaned up, and your main.tf file is still intact. You could run terraform apply again right now and recreate everything from scratch.
Your VPC networking stack is gone, but your code is permanent. Next up, you will make your configuration even more flexible with variables and outputs in the Secret Mission.
Secret mission
Add Variables and Outputs
Your Terraform configuration works, but the CIDR blocks are hardcoded. What if you wanted to reuse this setup for a different IP range? In this secret mission, you'll extract values into variables and add outputs that display resource IDs after every apply.
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. All resources provisioned by Terraform in this project are free VPC networking components with no hourly charges.
Resources you used:
- AWS VPC resources (VPC, subnets, internet gateway, route tables, security group, network ACLs).
- Local project directory nextwork-vpc-terraform with main.tf, variables.tf, outputs.tf, and Terraform state files.
- Terraform CLI (installed via Homebrew).
Keep everything running
No action needed. Choose this if you are still actively experimenting with your Terraform configuration or plan to extend the project further.
- Your AWS VPC resources are free networking components. They do not incur any hourly charges.
- Your local nextwork-vpc-terraform directory stays on your Desktop for future use.
- Terraform remains installed on your machine for other projects.
Pause - I'll come back to this later
Destroy the AWS resources but keep your local files. You can recreate everything later with a single terraform apply.
- Open your terminal and navigate to the nextwork-vpc-terraform folder on your Desktop.
- Run the following command to destroy all AWS resources:
terraform destroy
- Type yes when prompted to confirm.
- Your local files stay intact. When you are ready to resume, run terraform apply from the same directory to recreate everything.
Delete - I don't want to use this again
Remove all AWS resources and delete your local project files entirely.
- Open your terminal and navigate to the nextwork-vpc-terraform folder on your Desktop.
- Destroy all AWS resources by running:
terraform destroy
- Type yes when prompted to confirm.
- Move back to your Desktop and delete the project folder:
cd ~/Desktop
rm -rf nextwork-vpc-terraform
- On Windows, use this command instead:
cd ~\Desktop
rmdir /s /q nextwork-vpc-terraform
- To uninstall Terraform from your machine, run:
brew uninstall hashicorp/tap/terraform
- Confirm your AWS resources are gone by opening the VPC section of the AWS Console. You should see only the default VPC.
Nice Work!
Nice Work!
You just went from clicking through the AWS Console to provisioning an entire VPC networking stack with a single command. That is Infrastructure as Code in action.
You've learned how to:
- Wrote Terraform HCL configuration defining a VPC, subnets, an internet gateway, route tables, a security group, and network ACLs.
- Used terraform init, terraform plan, terraform apply, and terraform destroy to manage the full lifecycle of AWS infrastructure.
- Understood the difference between public and private subnets at the infrastructure-as-code level, and experienced the power of terraform destroy for instant cleanup.
- Secret Mission: Added variables and outputs to make your configuration reusable and informative.
Ready to quiz yourself?