Deploy a Dockerized App with Terraform
Provision a full AWS environment and deploy a containerized app using Terraform.
Introduction
30 Second Summary
Every time you click through a cloud console to create a server, you are making a decision that nobody else can reproduce. One misconfigured setting, one forgotten security rule, and your entire deployment can break with no way to trace what went wrong.
In this project, you will write Terraform configuration files that provision an entire AWS environment and deploy a Dockerized web application, all with a single command.
What You'll Build
A fully automated infrastructure deployment pipeline using Infrastructure as Code that provisions AWS networking, compute resources, and runs a Docker container serving a live web page.
By the end of this project, you'll have:
- A complete Terraform configuration that provisions a VPC, subnet, internet gateway, security group, and EC2 instance from scratch.
- A Dockerized Nginx application running on the provisioned EC2 instance, accessible via a public URL in your browser.
- Proof that you can destroy all AWS infrastructure with a single command, leaving zero resources running.
- Secret Mission: Add SSH access to your instance and connect to inspect the running container from inside.
Are there any prerequisites?
You'll need an AWS account with Free Tier remaining, a GitHub account, and basic familiarity with Docker and Linux command line concepts.
No prior Terraform experience is required. This project teaches you from scratch.
Install and Configure Your Tools
Before you can define infrastructure as code, you need two tools on your machine. Terraform reads your configuration files and talks to cloud providers. The AWS CLI authenticates those requests so AWS knows who is asking.
In this step you will install both tools, prove they can reach your AWS account, and then create the project files that define a full network from scratch.
In this step, get ready to:
- Install Terraform and AWS CLI v2.
- Configure AWS credentials for your account.
- Create and initialize your Terraform project with VPC networking code.
Install Terraform
Terraform is the tool that converts your .tf files into real cloud resources. It needs to be installed on your Linux machine before anything else.
- Check whether Terraform is already installed by running this command:
terraform version
✔️ I see version 1.0 or higher
You are good to go. Any version 1.0 or above works for this project. Move on to the next substep.
ⓧ I see an older version
Your installed version is too old. Remove it and reinstall using the steps in the next tab.
ⓧ Command not found
Terraform is not installed yet. Add the HashiCorp APT repository and install it by running these commands:
wget -O - https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(grep -oP '(?<=UBUNTU_CODENAME=).*' /etc/os-release || lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install terraform
- Confirm the install worked by running:
terraform version
You should see a line starting with Terraform v1 followed by the version number.
Install AWS CLI v2 and configure credentials
The AWS CLI is how Terraform authenticates to your AWS account. You will install it and then store your IAM credentials locally.
- Check whether AWS CLI is already installed by running:
aws --version
✔️ I see aws-cli/2.x.x
AWS CLI v2 is ready. Skip ahead to the credential configuration below.
ⓧ I see aws-cli/1.x.x
Version 1 does not support all the features this project needs. Install version 2 alongside it by running:
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install
- Verify the upgrade by running:
aws --version
You should now see aws-cli/2 at the start of the output.
ⓧ Command not found
AWS CLI is not installed yet. Install it by running:
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install
- Verify the install by running:
aws --version
You should see aws-cli/2 at the start of the output.
Now configure your credentials so Terraform can authenticate with AWS.
- Run the interactive configuration command:
aws configure
What will this command ask?
The command prompts you for four values. Enter your IAM access key ID, secret access key, and us-east-1 as the default region. For the output format prompt, press Enter to accept the default.
- Confirm your credentials work by running:
aws sts get-caller-identity
You should see a JSON object with your account ID, user ID, and ARN. This proves Terraform can reach AWS on your behalf.
Seeing an InvalidClientTokenId error?
Double-check that you copied the full access key and secret key from your IAM console. Run aws configure again and paste the values carefully.
help me fix my AWS CLI credentials
Create your project and define the network
With both tools authenticated, you can create the Terraform project. This project will contain two files that define an entire AWS network.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir terraform-docker-app
cd terraform-docker-app
- Confirm the folder exists by running:
ls ~/Desktop
You should see terraform-docker-app in the output.
The first file tells Terraform which cloud provider to use and which version constraints to enforce.
- Create providers.tf by running this command:
touch providers.tf
- Open providers.tf in your text editor and paste the following code:
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "us-east-1"
}
What does this file do?
- required_version = ">= 1.0" ensures anyone running this project has Terraform 1.0 or newer.
- source = "hashicorp/aws" tells Terraform to download the official AWS provider plugin from the Terraform Registry.
- version = "~> 5.0" allows any 5.x release but blocks 6.0 and above, preventing breaking changes.
- region = "us-east-1" sets the default AWS region for every resource you create.
- Save providers.tf.
Next you will define the actual network resources. A VPC is a virtual private network inside AWS. Instances you launch later need a VPC, a subnet, an internet gateway, and a route table to reach the public internet.
- Create main.tf by running:
touch main.tf
- Open main.tf and paste the VPC and subnet resources:
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "terraform-docker-vpc"
}
}
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
map_public_ip_on_launch = true
availability_zone = "us-east-1a"
tags = {
Name = "terraform-docker-public-subnet"
}
}
What do these resources do?
- aws_vpc "main" creates a virtual network with a 10.0.0.0/16 address range, giving you 65,536 possible IP addresses.
- enable_dns_support and enable_dns_hostnames let instances inside the VPC resolve domain names and receive public DNS hostnames.
- aws_subnet "public" carves out a smaller 10.0.1.0/24 block (256 addresses) where your instance will live.
- map_public_ip_on_launch = true automatically assigns a public IP to any instance launched in this subnet.
- Below the subnet resource, add the internet gateway, route table, and route table association:
resource "aws_internet_gateway" "igw" {
vpc_id = aws_vpc.main.id
tags = {
Name = "terraform-docker-igw"
}
}
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.igw.id
}
tags = {
Name = "terraform-docker-public-rt"
}
}
resource "aws_route_table_association" "public" {
subnet_id = aws_subnet.public.id
route_table_id = aws_route_table.public.id
}
What do these resources do?
- aws_internet_gateway "igw" connects your VPC to the public internet. Without it, nothing inside the VPC can reach the outside world.
- aws_route_table "public" defines a routing rule that sends all outbound traffic (0.0.0.0/0) through the internet gateway.
- aws_route_table_association "public" links the route table to your subnet so instances in that subnet use these routes.
- Save main.tf.
✔️ Awesome, I've got everything!
Great. Double check you have saved both providers.tf and main.tf.
ⓧ I'd like to double check the full code
Here is the complete providers.tf:
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "us-east-1"
}
Here is the complete main.tf:
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "terraform-docker-vpc"
}
}
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
map_public_ip_on_launch = true
availability_zone = "us-east-1a"
tags = {
Name = "terraform-docker-public-subnet"
}
}
resource "aws_internet_gateway" "igw" {
vpc_id = aws_vpc.main.id
tags = {
Name = "terraform-docker-igw"
}
}
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.igw.id
}
tags = {
Name = "terraform-docker-public-rt"
}
}
resource "aws_route_table_association" "public" {
subnet_id = aws_subnet.public.id
route_table_id = aws_route_table.public.id
}
The final step is to initialize Terraform. This downloads the AWS provider plugin so Terraform knows how to create AWS resources.
- Initialize the project by running:
terraform init
You should see Terraform has been successfully initialized! at the end of the output.
What did terraform init do?
It read providers.tf, found the hashicorp/aws requirement, downloaded the provider plugin, and created a .terraform.lock.hcl file to pin the exact version it fetched.
You only need to run terraform init once per project, or again if you change provider requirements.
Seeing a provider download error?
Make sure you are inside the terraform-docker-app directory where providers.tf lives. Run ls to confirm you see both providers.tf and main.tf.
Also check your internet connection. Terraform must reach registry.terraform.io to download the plugin.
help me fix terraform init
Your project is initialized and your network is defined in code. Next up, you will add a security group and an EC2 instance that runs a Docker container.
Define the AWS Provider and VPC Networking
Your network foundation is in place. Terraform downloaded the AWS provider plugin and knows how to authenticate with your account.
Now you need the pieces that actually do something inside that network. A security group controls which traffic reaches your instance, and an EC2 instance is the virtual server that runs your Docker container.
In this step, get ready to:
- Add a security group that allows HTTP traffic into your VPC.
- Define an EC2 instance that automatically installs Docker and runs Nginx on boot.
- Create an outputs file to expose the instance's public IP address.
Add a security group for HTTP traffic
Before your instance can serve web traffic, you need a firewall rule that explicitly allows incoming HTTP requests on port 80. Without this, AWS blocks all inbound connections by default.
- In main.tf, add the following security group resource below the aws_route_table_association block:
resource "aws_security_group" "web" {
name = "terraform-docker-web-sg"
description = "Allow HTTP inbound and all outbound traffic"
vpc_id = aws_vpc.main.id
# Allow incoming HTTP traffic on port 80 from any IP
ingress {
description = "HTTP from anywhere"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
# Allow all outbound traffic so the instance can download packages
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "terraform-docker-web-sg"
}
}
What does this code do?
The ingress block opens port 80 (HTTP) to all IP addresses. This lets browsers reach your web server once it's running.
The egress block with protocol -1 allows all outbound traffic. Your instance needs this to download Docker images and system updates.
Setting vpc_id = aws_vpc.main.id attaches this security group to the VPC you defined in the previous step.
- Save main.tf.
- You won't see a change yet. The next chunk defines the EC2 instance that uses this security group.
Define the AMI lookup and EC2 instance
Every EC2 instance needs a machine image (AMI) that defines its operating system. Instead of hardcoding an AMI ID that changes per region and over time, you'll use a data source that dynamically finds the latest Amazon Linux 2023 image.
- In main.tf, add the following data source and instance resource below the security group you just created:
data "aws_ami" "amazon_linux" {
most_recent = true
owners = ["amazon"]
# Match Amazon Linux 2023 x86_64 images
filter {
name = "name"
values = ["al2023-ami-2023.*-x86_64"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
}
resource "aws_instance" "web" {
ami = data.aws_ami.amazon_linux.id
instance_type = "t2.micro"
subnet_id = aws_subnet.public.id
vpc_security_group_ids = [aws_security_group.web.id]
# Boot script: install Docker and run Nginx container
user_data = <<-EOF
#!/bin/bash
dnf update -y
dnf install -y docker
systemctl start docker
systemctl enable docker
docker run -d -p 80:80 nginx:latest
EOF
tags = {
Name = "terraform-docker-web"
}
}
What does this code do?
The data "aws_ami" block queries AWS for the latest Amazon Linux 2023 AMI at apply time. This means your configuration never goes stale when Amazon publishes updated images.
The user_data script runs automatically when the instance first boots. It installs Docker, starts the daemon, and launches an Nginx container mapped to port 80. By the time you visit the public IP, the web server is already running.
Using vpc_security_group_ids (not security_groups) is required when launching into a VPC subnet.
- Save main.tf.
- You won't see visible output yet. The next substep creates an outputs file, and Step 4 runs terraform plan to preview the full deployment.
Seeing a syntax error when you save?
Make sure the <<-EOF heredoc has a matching EOF closing marker on its own line. The indentation before EOF must use spaces, not tabs.
Check that each block (data, resource) has matching opening and closing braces.
help me fix a syntax error in my Terraform main.tf file
Create the outputs file
Terraform can expose values from your infrastructure after deployment. An outputs file tells Terraform which values to print to your terminal so you know where to find your running app.
- Create a new file called outputs.tf in your terraform-docker-app directory by running:
touch outputs.tf
- Open outputs.tf and add the following output definitions:
output "instance_public_ip" {
description = "Public IP address of the web server"
value = aws_instance.web.public_ip
}
output "web_url" {
description = "URL to access the web application"
value = "http://${aws_instance.web.public_ip}"
}
What does this code do?
The instance_public_ip output prints the raw IP address of your EC2 instance after terraform apply finishes.
The web_url output wraps that IP into a full URL you can paste directly into your browser.
- Save outputs.tf.
- Confirm all three Terraform files exist by running:
ls *.tf
- You should see main.tf, outputs.tf, and providers.tf listed in the output.
Missing a file?
Make sure you're in the terraform-docker-app directory. Run pwd to confirm your current location.
If outputs.tf is missing, the touch command may have run in the wrong directory. Re-run it from inside terraform-docker-app.
help me find my missing Terraform files
Now double check your full code for each file to make sure everything is in place.
✔️ Awesome, I've got everything!
Great. Make sure you've saved all three files before moving on.
ⓧ I'd like to double check the full code
Here is the full main.tf file with all resources:
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "terraform-docker-vpc"
}
}
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
map_public_ip_on_launch = true
availability_zone = "us-east-1a"
tags = {
Name = "terraform-docker-public-subnet"
}
}
resource "aws_internet_gateway" "igw" {
vpc_id = aws_vpc.main.id
tags = {
Name = "terraform-docker-igw"
}
}
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.igw.id
}
tags = {
Name = "terraform-docker-public-rt"
}
}
resource "aws_route_table_association" "public" {
subnet_id = aws_subnet.public.id
route_table_id = aws_route_table.public.id
}
resource "aws_security_group" "web" {
name = "terraform-docker-web-sg"
description = "Allow HTTP inbound and all outbound traffic"
vpc_id = aws_vpc.main.id
ingress {
description = "HTTP from anywhere"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "terraform-docker-web-sg"
}
}
data "aws_ami" "amazon_linux" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["al2023-ami-2023.*-x86_64"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
}
resource "aws_instance" "web" {
ami = data.aws_ami.amazon_linux.id
instance_type = "t2.micro"
subnet_id = aws_subnet.public.id
vpc_security_group_ids = [aws_security_group.web.id]
user_data = <<-EOF
#!/bin/bash
dnf update -y
dnf install -y docker
systemctl start docker
systemctl enable docker
docker run -d -p 80:80 nginx:latest
EOF
tags = {
Name = "terraform-docker-web"
}
}
Here is the full outputs.tf file:
output "instance_public_ip" {
description = "Public IP address of the web server"
value = aws_instance.web.public_ip
}
output "web_url" {
description = "URL to access the web application"
value = "http://${aws_instance.web.public_ip}"
}
And providers.tf remains unchanged from the previous step:
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "us-east-1"
}
Your Terraform configuration is complete. Next up, you'll run terraform plan to preview everything Terraform will create, then terraform apply to deploy it live.
Add Security Group and EC2 Instance
Your infrastructure is fully defined in code. You have a VPC with public networking, a security group allowing HTTP traffic, and an EC2 instance configured to run Docker on boot.
Now it's time to bring that code to life. Terraform's plan/apply workflow lets you preview exactly what will be created before committing any real resources on AWS.
In this step, get ready to:
- Preview your infrastructure changes with terraform plan.
- Deploy all resources with terraform apply.
- Verify the Nginx container is serving traffic in your browser.
Preview the deployment plan
Before creating any resources, Terraform can show you a detailed preview of every change it will make. This is your safety net to catch mistakes before they cost time or money.
- Preview all resources Terraform will create by running this command:
terraform plan
What does this command do?
Terraform reads your .tf files and compares the desired state (your code) against the current state (nothing yet). It then outputs a line-by-line preview of every resource it will create.
You should see Plan: 7 to add, 0 to change, 0 to destroy at the bottom. The 7 resources are: VPC, subnet, internet gateway, route table, route table association, security group, and EC2 instance.
- Scroll through the output to see each resource listed with a + symbol, indicating it will be created.
Why preview before deploying?
In production, a single typo in your Terraform code could delete a database or open a port to the internet. Running terraform plan first lets you catch problems while they're still free to fix.
Seeing errors in your plan?
If the plan fails with authentication errors, return to your terminal and run aws sts get-caller-identity to confirm your credentials are still valid.
If you see a syntax error referencing a specific line in main.tf, double check that file matches the code from the previous step.
Still stuck? Help me debug my terraform plan error
Deploy your infrastructure
With a clean plan confirmed, you can now tell Terraform to create all 7 resources on AWS. This is where your code becomes real cloud infrastructure.
- Deploy your infrastructure by running this command:
terraform apply
- Terraform shows the same plan preview, then asks for confirmation.
- Type yes and press Enter to confirm.
What happens during apply?
Terraform sends API calls to AWS in dependency order. It creates the VPC first, then the subnet (which depends on the VPC), then the internet gateway, and so on. The EC2 instance is created last because it depends on the subnet and security group.
This usually takes 30-60 seconds. When finished, you'll see the outputs you defined: instance_public_ip and web_url.
- Note the instance_public_ip value displayed in your terminal output. You will use this IP address to access your web application.
Apply failed with a permissions error?
Your IAM user needs permissions to create VPCs, subnets, internet gateways, security groups, and EC2 instances. Attach the AmazonEC2FullAccess managed policy to your IAM user, then run terraform apply again.
If you see a VcpuLimitExceeded error, your account has reached its vCPU limit in us-east-1. Request a limit increase through the AWS Service Quotas console.
Need more help? Help me fix my terraform apply error
Verify the running application
Your EC2 instance is now running, but it needs a minute or two to finish its boot script. The user_data script installs Docker and pulls the Nginx image before the web server starts responding.
- Wait 1-2 minutes for the instance's boot script to complete.
- Copy the web_url output from your terminal (or construct it as http:// followed by your public IP).
- Paste the URL into your browser's address bar and press Enter.
You should see the Nginx welcome page with the heading "Welcome to nginx!" in your browser.
What just happened?
When the EC2 instance booted, it ran your user_data bash script automatically. That script updated packages, installed Docker, started the Docker daemon, and ran an Nginx container mapped to port 80.
Traffic flows from your browser through the internet gateway, into the VPC's public subnet, past the security group (which allows port 80), and hits the Nginx container inside Docker on the instance.
Not seeing the Nginx page?
If the page times out, wait another minute. The user_data script can take up to 3 minutes on first boot while it downloads packages and the Docker image.
Make sure you are using http:// (not https://) in your browser. The security group only allows port 80 (HTTP), not port 443 (HTTPS).
If it still does not load, verify in the AWS Console that your instance's state is "running" and it has a public IP assigned.
Still stuck? Help me troubleshoot my EC2 instance
Your infrastructure is live and serving traffic. Every piece of it is defined in code and can be reproduced on demand. Next up, you will tear it all down with a single command to prove the full lifecycle.
Deploy and Verify Your Infrastructure
You've seen Terraform create an entire AWS environment from code. The real power of Infrastructure as Code goes beyond provisioning. It also gives you the ability to cleanly tear everything down with a single command.
In this step, you'll run terraform destroy to remove every resource Terraform created. Then you'll verify in the AWS Console that nothing remains from this project.
In this step, get ready to:
- Destroy all AWS infrastructure with a single Terraform command.
- Verify in the AWS Console that no project resources remain.
Run terraform destroy to tear down all resources
The terraform destroy command reads your state file and removes every resource Terraform manages. It is the mirror image of terraform apply.
- Destroy all your AWS infrastructure by running this command:
terraform destroy
Terraform will display a destruction plan listing every resource it intends to delete, then prompt you for confirmation.
- Type yes and press Enter to confirm the destruction.
What does terraform destroy do?
Terraform reads its state file to identify every resource it created. It calculates the correct deletion order (for example, the EC2 instance is deleted before the subnet, and the subnet before the VPC).
After you confirm, Terraform deletes each resource from AWS and removes it from the local state file. Your state becomes empty, proving zero resources remain under management.
You should see output ending with a line that says Destroy complete! Resources: 7 destroyed. (the count matches the number of resources Terraform created).
Seeing an error during destroy?
Make sure you are running the command from inside the terraform-docker-app directory where your .tf files live.
If you see a permissions error, confirm your AWS CLI credentials are still configured by running aws sts get-caller-identity. If that fails, re-run aws configure with valid credentials.
help me troubleshoot a terraform destroy error
Verify in the AWS Console that no resources remain
The terminal output confirms deletion, but checking the AWS Console gives you visual proof that nothing is running.
- Open the AWS Console in your browser at https://console.aws.amazon.com.
- In the top search bar, type EC2 and select EC2 from the results.
- Click Instances in the left sidebar.
- Confirm that no instance named terraform-docker-web appears as running. It may briefly show as terminated before disappearing entirely.
- In the top search bar, type VPC and select VPC from the results.
- Click Your VPCs in the left sidebar.
- Confirm that no VPC named terraform-docker-vpc exists. Only the default VPC should remain.
Why verify in the console?
Checking the console proves that the Terraform state file and the real AWS environment are in sync. If a resource appeared in the console but not in your state, it would be an orphan that Terraform cannot manage. Seeing a clean console confirms your IaC workflow is airtight.
Check your billing dashboard too
Navigate to Billing and Cost Management in the AWS Console to confirm no unexpected charges are accumulating. With all resources destroyed, your bill for this project should be $0.
Destroy All Infrastructure
You have a live web app running on AWS. Every piece of infrastructure was defined in code and deployed with a single command.
The real power of Infrastructure as Code shows up when it is time to clean up. Instead of clicking through the AWS Console deleting resources one by one, you can tear down everything with a single terraform destroy command.
In this step, get ready to:
- Destroy all AWS resources with one command.
- Verify in the AWS Console that nothing remains.
Run terraform destroy
The terraform destroy command reads your state file, determines which resources exist, and generates a plan to delete all of them. It is the inverse of terraform apply.
- Destroy all your AWS infrastructure by running this command:
terraform destroy
Terraform will display a destroy plan listing every resource it intends to delete. It then prompts you for confirmation.
- Review the plan. You should see 8 resources marked for destruction (VPC, subnet, internet gateway, route table, route table association, security group, AMI data source read, and EC2 instance).
- Type yes when Terraform asks you to confirm, then press Enter.
What does this command do?
Terraform reads the state file (terraform.tfstate) to find every resource it manages. It contacts the AWS API and deletes each one in the correct dependency order.
For example, Terraform deletes the EC2 instance before the subnet, and the subnet before the VPC. This order matters because AWS will not let you delete a VPC that still has child resources attached.
- Wait for the destruction to complete. The EC2 instance takes the longest (around 30-60 seconds).
- Confirm you see a message ending with Destroy complete! Resources: 8 destroyed. in your terminal output.
Seeing a dependency error?
If Terraform reports it cannot delete a resource because of a dependency, run terraform destroy again. Transient AWS API timing issues can cause a first attempt to fail, but a retry usually succeeds.
If you accidentally cancelled mid-destroy, some resources may already be gone. Running terraform destroy again will refresh state and clean up anything remaining.
help me troubleshoot a terraform destroy failure
Verify in the AWS Console
The terminal output confirms the destroy succeeded, but it is good practice to verify directly in the AWS Console. This builds confidence that your infrastructure is truly gone and no surprise charges will appear.
- Open the AWS Console in your browser and sign in.
- Make sure you are in the us-east-1 region (check the region selector in the top right).
- Navigate to VPC using the search bar at the top. Look at the Your VPCs list.
- Confirm there is no VPC named terraform-docker-vpc.
- Navigate to EC2 using the search bar. Click Instances in the left sidebar.
- Confirm there is no instance named terraform-docker-web in a running or stopped state.
What about the default VPC?
You will still see a default VPC in your account. That is the one AWS creates automatically for every region. It is not related to this project and should not be deleted.
Terraform state is now empty
After a successful destroy, your terraform.tfstate file still exists but contains zero resources. Your .tf files are unchanged. You could run terraform apply again right now and recreate everything from scratch.
Secret mission
Add SSH Access to Your Instance
You can deploy infrastructure and see your app running in a browser. But what if you need to get inside the instance to debug a container, check logs, or inspect running processes? In this secret mission, you will add SSH access to your Terraform configuration, connect to your EC2 instance directly, and verify the Docker container is running from inside the server.
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your local project files and tools for future use, or remove them entirely. If you followed Step 5 or the Secret Mission, your AWS infrastructure is already destroyed.
Cost warning
The EC2 t2.micro instance is covered by AWS Free Tier (750 hours/month for 12 months) for accounts created before July 15, 2025. VPC, subnet, internet gateway, route table, and security group resources are free.
If you skipped terraform destroy for any reason, the t2.micro instance costs approximately $0.0116/hour in us-east-1 once Free Tier expires. Run terraform destroy immediately to avoid charges.
Resources you used:
- AWS infrastructure (VPC, subnet, internet gateway, route table, security group, EC2 instance) managed by Terraform.
- Local project directory terraform-docker-app containing main.tf, providers.tf, and outputs.tf.
- SSH key pair at ~/.ssh/terraform-docker-key and ~/.ssh/terraform-docker-key.pub.
- Terraform CLI (installed via APT).
- AWS CLI v2 (installed via command line installer).
- AWS credentials stored in ~/.aws/.
Keep everything running
No action needed. Choose this if you plan to continue experimenting with Terraform and AWS.
- Your local project files remain in the terraform-docker-app directory for future use.
- Terraform and AWS CLI stay installed and ready for your next project.
- Your SSH key pair remains at ~/.ssh/terraform-docker-key for reuse.
- Your AWS credentials remain configured in ~/.aws/ for future CLI use.
- If you re-run terraform apply later, remember the EC2 instance consumes Free Tier hours while running.
Pause - I'll come back to this later
Your AWS infrastructure is already destroyed if you completed Step 5 or the Secret Mission. No cloud resources are running.
- Keep your local project files so you can re-deploy later with a single terraform apply command.
- Keep Terraform and AWS CLI installed so you do not need to reinstall them next time.
- Verify your infrastructure is destroyed by running:
terraform state list
If the output is empty, no AWS resources are running and you have no ongoing costs.
Delete - I don't want to use this again
Remove all project resources and start fresh. Follow each step below.
- If you have not already run terraform destroy, do so now to remove all AWS resources:
terraform destroy
- Type yes when prompted to confirm.
- Delete your local project directory by running:
cd ~
rm -rf ~/Desktop/terraform-docker-app
- Delete the SSH key pair you generated for this project:
rm ~/.ssh/terraform-docker-key
rm ~/.ssh/terraform-docker-key.pub
- Uninstall Terraform (installed via APT):
sudo apt remove terraform
- Uninstall AWS CLI v2 (installed via the command line installer):
sudo rm /usr/local/bin/aws
sudo rm /usr/local/bin/aws_completer
sudo rm -rf /usr/local/aws-cli
- Optionally remove your stored AWS credentials:
rm -rf ~/.aws
- Verify the AWS Console shows no lingering resources. Open the VPC dashboard in the us-east-1 region and confirm no VPC named terraform-docker-vpc exists.
- Open the EC2 dashboard and confirm no instance named terraform-docker-web exists.
That's a Wrap!
That's a Wrap!
Nice work! You just built a complete Infrastructure as Code pipeline that provisions, deploys, and destroys an entire AWS environment from version-controlled Terraform files.
You've learned how to:
- Write Terraform configuration files that provision a full AWS VPC networking stack from scratch, including subnets, internet gateways, route tables, and security groups.
- Deploy a Dockerized Nginx web app to an EC2 instance using user_data automation, accessible from a public IP in your browser.
- Use the Terraform plan/apply/destroy lifecycle to preview, create, and tear down entire cloud environments with a single command.
- Secret Mission: Added SSH access to your instance, connected remotely, and inspected the running Docker container.
Ready to quiz yourself?