Deploy an EC2 Instance with Terraform

Provision a real EC2 instance on AWS using Terraform code.

Introduction

30 Second Summary

Every time you click through a cloud console to create a server, you are making a decision that no one else can reliably reproduce. One misclick or one forgotten setting, and hours disappear into troubleshooting.

In this project, you will use Terraform to deploy a real EC2 instance on AWS entirely through code. You write configuration files, preview exactly what will change, and watch your infrastructure appear with a single command.

What You'll Build

You run terraform apply in your terminal and a fully configured EC2 instance spins up on AWS. You change one variable, re-run the command, and Terraform updates only what changed.

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

  • A running EC2 instance deployed entirely from code, visible in your AWS console with no manual clicks involved.
  • A reusable Terraform project with variables and outputs that you can modify and re-deploy in seconds.
  • Hands-on experience with the full plan/apply/destroy lifecycle, where you see exactly what will change before anything is touched.
  • Secret Mission: Add a security group that opens SSH access to your instance.

Are there any prerequisites?

You need an AWS account with IAM credentials ready (an access key and secret key). You should also be comfortable running basic commands in a terminal.

Before We Start

Before We Start...

Before we dive in, let's lock in what you're about to build and why it matters. Instead of clicking through the AWS console to create resources one by one, you'll write code that Terraform reads, plans, and executes for you.

Set Up Terraform and AWS Credentials

Terraform runs on your local machine and communicates with AWS to create, modify, and destroy cloud resources. For this to work, you need two things: Terraform itself, and a way for Terraform to prove its identity to AWS.

In this step, you'll install Terraform and the AWS CLI, then configure your AWS credentials so Terraform can authenticate on your behalf.

In this step, get ready to:
  • Install Terraform on Linux.
  • Install and configure AWS CLI v2 with your credentials.
  • Verify both tools can communicate with AWS.
Install Terraform on Linux

Terraform is distributed as a single binary. On Ubuntu and Debian-based Linux, the easiest way to install it is through HashiCorp's official APT repository.

  • Check if Terraform is already installed by running this command:
terraform version

✔️ I see version 1.2 or higher

Terraform is ready to go. Skip ahead to the next substep.

ⓧ I see an older version

Your version is too old for this project. Reinstall Terraform from the HashiCorp APT repository to get the latest version.

  • Add the HashiCorp GPG key and repository, then install the latest Terraform 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 installation by running:
terraform version

You should see a version number starting with Terraform v1. confirming the upgrade worked.

ⓧ Command not found

Terraform is not installed yet. Install it from the official HashiCorp APT repository.

  • Add the HashiCorp GPG key and repository, then install Terraform 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 installation by running:
terraform version

You should see a version number starting with Terraform v1. confirming the install succeeded.

What do these install commands do?

The first command downloads HashiCorp's GPG key so your system can verify that packages really come from HashiCorp. The second command adds the HashiCorp APT repository to your system's list of software sources.

The third command refreshes the package list and installs the terraform binary. After this, you can run terraform from any directory.

Install and configure AWS CLI v2

The AWS CLI is the command-line tool that lets you interact with AWS services. Terraform uses the same credential configuration as the AWS CLI, so configuring one configures both.

  • Check if AWS CLI v2 is already installed by running this command:
aws --version

✔️ I see aws-cli/2.x.x

AWS CLI v2 is already installed. Skip ahead to the credential configuration below.

ⓧ I see aws-cli/1.x.x

AWS CLI v1 will not work for this project. Install AWS CLI v2 alongside it. The v2 installer creates a new binary at /usr/local/bin/aws which takes priority over v1.

  • Download and install AWS CLI v2 by running these commands:
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install
  • Confirm you now have v2 by running:
aws --version

You should see output starting with aws-cli/2. confirming v2 is active.

ⓧ Command not found

AWS CLI is not installed yet. Install v2 using the official installer.

  • Download and install AWS CLI v2 by running these commands:
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install
  • Confirm the installation by running:
aws --version

You should see output starting with aws-cli/2. confirming the install succeeded.

Seeing 'unzip: command not found'?

Some minimal Linux installations do not include unzip by default. Install it first with sudo apt install unzip, then re-run the AWS CLI install commands above.

Still stuck? Help me install AWS CLI v2 on my Linux system.

Now that the AWS CLI is installed, you need to configure it with your AWS credentials. This is how Terraform will authenticate when creating resources.

What credentials do I need?

You need an AWS Access Key ID and Secret Access Key from an IAM user in your AWS account. If you do not have these yet, create them in the AWS IAM console under your user's **Security credentials** tab.

Never share these credentials or commit them to version control.

  • Configure your AWS credentials by running this command:
aws configure

What will aws configure ask me?

The command prompts you for four values, one at a time:

  • AWS Access Key ID: paste your access key.
  • AWS Secret Access Key: paste your secret key.
  • Default region name: enter us-east-1.
  • Default output format: enter json.
  • Enter each value when prompted. For the region, type us-east-1. For the output format, type json.
Verify both tools work

Both tools are installed and configured. Before you move on, confirm that everything is connected properly.

Before you run the next two commands, what do you expect to see? Think about what each tool needs to prove it is working.

  • Verify Terraform is installed by running this command:
terraform version

You should see output that starts with Terraform v1.15 (or a similar v1.x version). This confirms Terraform is installed and accessible from your terminal.

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

You should see a JSON response containing your AWS account ID, user ARN, and user ID. It looks like this:

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

If you see your account ID in the Account field, your credentials are correctly configured. Terraform will use these same credentials to create resources in your AWS account.

Seeing 'Unable to locate credentials'?

This means aws configure did not save your credentials correctly. Run aws configure again and double-check you are pasting the full Access Key ID and Secret Access Key without extra spaces.

Make sure you are using credentials from an IAM user (not the root account email and password). IAM credentials start with AKIA for the Access Key ID.

Need more help? Help me fix my AWS CLI credentials configuration.

Your environment is ready. Next up, you'll write your first Terraform configuration and watch it create a real EC2 instance on AWS.

Write Your First Terraform Config and Deploy

Your tools are ready and AWS is listening. Now for the moment of truth.

The fastest way to understand Infrastructure as Code is to see it work. In this step, you'll write a minimal Terraform configuration and watch it create a real EC2 instance on AWS.

In this step, get ready to:
  • Create a project directory with Terraform configuration files.
  • Initialize Terraform and preview what it will build.
  • Deploy a real EC2 instance and confirm it's running in AWS.
Create your project directory and config files

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 terraform-ec2
cd terraform-ec2

Now you'll create the first configuration file. This file tells Terraform which provider (AWS) to download and which version constraints to use.

  • Create a file called terraform.tf in your terraform-ec2 folder using your preferred text editor, and paste this code into it:
# Pin the AWS provider version
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.92"
    }
  }

  required_version = ">= 1.2"
}

What does this code do?

The required_providers block tells Terraform to download the AWS provider from HashiCorp's registry. The ~> 5.92 version constraint means any 5.x version at 5.92 or higher.

The required_version line ensures anyone running this project has at least Terraform 1.2 installed.

  • Save terraform.tf.
  • Confirm the file exists by running:
ls

You should see terraform.tf in the output.

Next, you'll write main.tf which defines where to deploy and what to deploy. You'll start with the provider configuration and a data source to find the right Ubuntu machine image.

  • Create a file called main.tf in the same folder, and paste this code into it:
# Tell the AWS provider to deploy in us-east-1
provider "aws" {
  region = "us-east-1"
}

# Look up the latest Ubuntu 24.04 AMI from Canonical
data "aws_ami" "ubuntu" {
  most_recent = true

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-*"]
  }

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

  owners = ["099720109477"]
}

What does this code do?

The provider block configures the AWS provider to target the us-east-1 region. Terraform uses the credentials you set up with aws configure in Step 1.

The data block is a query, not a resource. It searches AWS for the latest Ubuntu 24.04 AMI published by Canonical (owner ID 099720109477). This way your config always picks the most recent image without hardcoding an AMI ID that could go stale.

  • Now add the EC2 instance resource below the data source in the same main.tf file:
# Create an EC2 instance using the Ubuntu AMI
resource "aws_instance" "app_server" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t2.micro"

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

What does this code do?

The resource block declares a real piece of infrastructure Terraform will create. aws_instance is the resource type and app_server is the name you use to reference it in your config.

The ami argument references the data source output from above. The instance_type is t2.micro which falls under the AWS Free Tier. The Name tag makes your instance easy to find in the AWS console.

  • Save main.tf.

✔️ Awesome, I've got everything!

Great. Double check you've saved both terraform.tf and main.tf.

ⓧ I'd like to double check the full code

Here is the full terraform.tf:

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

  required_version = ">= 1.2"
}

Here is the full main.tf:

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

data "aws_ami" "ubuntu" {
  most_recent = true

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-*"]
  }

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

  owners = ["099720109477"]
}

resource "aws_instance" "app_server" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t2.micro"

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

File not showing up?

Make sure you are inside the terraform-ec2 directory when you run ls. If you only see one file, double check that you saved the other one to the same folder.

help me figure out why my Terraform files aren't showing up

Initialize Terraform and preview the plan

Before Terraform can do anything, it needs to download the AWS provider plugin you declared in terraform.tf. This is what terraform init does.

  • Initialize your Terraform project by running:
terraform init

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

What just happened?

Terraform created a hidden .terraform/ directory containing the downloaded AWS provider plugin. It also created .terraform.lock.hcl to lock the exact provider version for reproducible builds.

Seeing a provider error?

If you see an error about finding provider versions, make sure terraform.tf has source = "hashicorp/aws" spelled exactly as shown above. Also confirm you have internet connectivity.

help me fix my terraform init error

Now let's see what Terraform plans to create without actually creating anything.

  • Preview the deployment plan by running:
terraform plan

You should see output ending with Plan: 1 to add, 0 to change, 0 to destroy. which means Terraform will create exactly one resource: your EC2 instance.

What is a Terraform plan?

The plan is a dry run. Terraform compares your configuration to the current state (which is empty right now) and calculates what needs to change. The + create symbol next to each attribute means that value will be set when the resource is created.

Nothing in AWS has changed yet. The plan only shows what Terraform intends to do.

Deploy the instance

You've seen the plan. Time to make it real.

Before you run the next command, predict: will Terraform ask you for confirmation, or will it deploy immediately?

  • Deploy your EC2 instance by running:
terraform apply
  • Terraform shows the same plan and then asks Do you want to perform these actions? Type yes and press Enter to confirm.

Terraform creates the instance and prints Apply complete! Resources: 1 added, 0 changed, 0 destroyed. when it finishes.

Why does Terraform ask for confirmation?

This confirmation step is a safety net. In production, a single typo could destroy critical infrastructure. By showing you the plan and requiring yes, Terraform ensures you never accidentally modify resources.

  • Verify your instance is running by checking the AWS console or running:
aws ec2 describe-instances --filters "Name=tag:Name,Values=terraform-learn" --query "Reservations[].Instances[].State.Name" --output text

You should see running in the output. Your EC2 instance is live.

Seeing 'pending' instead of 'running'?

EC2 instances take a few seconds to fully boot. Wait 10-15 seconds and run the command again. If you see an empty result, double check your region is set to us-east-1 in both your AWS CLI config and your main.tf provider block.

help me verify my Terraform EC2 instance is running

You just deployed a real server on AWS with nothing but code. No clicking through the console, no manual configuration. Next up, you'll hit the limits of this hardcoded approach and introduce variables to make your config reusable.

Refactor with Variables and Outputs

Your EC2 instance is live on AWS. You deployed it from code in a single command.

But there's a problem with your current config. Every value is written directly into the resource block. If you need to change the instance name or type, you have to open the file and edit the resource itself.

In this step, get ready to:
  • Feel the fragility of hardcoded values by editing a resource directly.
  • Refactor your config to use variables for reusable, changeable values.
  • Add outputs to expose useful instance details without opening the AWS console.
Try changing a hardcoded value

Your instance is running with the Name tag set to terraform-learn. What if you wanted to rename it?

  • In main.tf, scroll down to the aws_instance resource block.
  • Find this line inside the tags block:
    Name = "terraform-learn"
  • Replace "terraform-learn" with "my-test-server", so the line now looks like this:
    Name = "my-test-server"
  • Save main.tf.

Before you run the next command, what do you think terraform plan will show? Will it want to create, change, or destroy anything?

  • Preview the change by running:
terraform plan

Terraform shows a plan to update the Name tag in-place. It detected that the tag in your config no longer matches what it deployed earlier, so it wants to fix the drift.

What does update in-place mean?

An in-place update means Terraform can change the attribute without destroying and recreating the resource. Tag changes are non-destructive. Other changes (like switching to a different AMI) would force a full replacement.

This works for one tag on one resource. Now imagine managing 50 resources across 10 files. Every rename means hunting through resource blocks, finding the right string, and editing it manually. That fragility is exactly what variables solve.

Create variables and update your config

Instead of reverting that edit manually, you will replace both hardcoded values with variable references. The variable defaults will match what Terraform originally deployed, so the state stays in sync.

  • Create a new file called variables.tf in your terraform-ec2 directory by running:
touch variables.tf
  • Open variables.tf and add the following variable definitions:
variable "instance_name" {
  description = "Value of the Name tag for the EC2 instance"
  type        = string
  default     = "terraform-learn"
}

variable "instance_type" {
  description = "EC2 instance type"
  type        = string
  default     = "t2.micro"
}

What does this code do?

Each variable block defines a named input for your config. The type constrains what values are accepted. The default provides a fallback value when no override is given.

By setting defaults to "terraform-learn" and "t2.micro", these match exactly what Terraform deployed in the previous step. That means swapping hardcoded strings for variable references changes nothing in the infrastructure.

  • Save variables.tf.

✔️ Awesome, I've got everything!

Great. Make sure you saved the file before moving on.

ⓧ I'd like to double check the full code

variable "instance_name" {
  description = "Value of the Name tag for the EC2 instance"
  type        = string
  default     = "terraform-learn"
}

variable "instance_type" {
  description = "EC2 instance type"
  type        = string
  default     = "t2.micro"
}

Now update main.tf to use these variables instead of the hardcoded strings.

  • In main.tf, find the aws_instance resource block. It currently looks like this:
resource "aws_instance" "app_server" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t2.micro"

  tags = {
    Name = "my-test-server"
  }
}
  • Replace the entire resource block with this version that uses variable references:
resource "aws_instance" "app_server" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = var.instance_type

  tags = {
    Name = var.instance_name
  }
}

What changed here?

You replaced "t2.micro" with var.instance_type and "my-test-server" with var.instance_name. Terraform resolves var. references at plan time by reading the matching variable block's default (or an override you provide at the command line).

  • Save main.tf.

✔️ Awesome, I've got everything!

Great. Double check you saved both variables.tf and main.tf.

ⓧ I'd like to double check the full code

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

data "aws_ami" "ubuntu" {
  most_recent = true

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-*"]
  }

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

  owners = ["099720109477"]
}

resource "aws_instance" "app_server" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = var.instance_type

  tags = {
    Name = var.instance_name
  }
}

Now the real test. Your variable defaults are "terraform-learn" and "t2.micro". The deployed instance already has those exact values in its state. Do you think Terraform will want to change anything, or will it see that nothing is different?

  • Confirm the refactor is clean by running:
terraform plan

You should see the message: No changes. Your infrastructure matches the configuration. This proves your variable defaults produce the exact same state as the original hardcoded values.

Seeing planned changes instead of no changes?

If Terraform wants to update the Name tag, your instance_name variable default might not be exactly "terraform-learn". Open variables.tf and confirm the default matches the original Name tag from Step 2.

If Terraform reports an error about an undefined variable, make sure variables.tf is saved in the same directory as main.tf. Terraform reads all .tf files in the current directory automatically.

Help me debug why terraform plan shows changes after adding variables.

Add outputs for instance details

Your instance is running, but to find its public IP or instance ID you would have to log into the AWS console. Outputs let Terraform expose those values directly in your terminal after every apply.

  • Create a new file called outputs.tf by running:
touch outputs.tf
  • Open outputs.tf and add the following output definitions:
output "instance_id" {
  description = "ID of the EC2 instance"
  value       = aws_instance.app_server.id
}

output "instance_public_ip" {
  description = "Public IP address of the EC2 instance"
  value       = aws_instance.app_server.public_ip
}

What does this code do?

Each output block tells Terraform to print a specific attribute after every apply. The value field references an attribute from a resource Terraform manages.

Here, aws_instance.app_server.id is the instance's unique ID and aws_instance.app_server.public_ip is its public IP address. You can query these anytime with terraform output instead of logging into the console.

  • Save outputs.tf.

✔️ Awesome, I've got everything!

Great. Make sure you saved the file before continuing.

ⓧ I'd like to double check the full code

output "instance_id" {
  description = "ID of the EC2 instance"
  value       = aws_instance.app_server.id
}

output "instance_public_ip" {
  description = "Public IP address of the EC2 instance"
  value       = aws_instance.app_server.public_ip
}
  • Register the new outputs by running:
terraform apply

What will this do?

Terraform will detect that no infrastructure changes are needed (since you only added outputs, not resource changes). It will prompt you to confirm with yes so it can save the new output values to state.

  • Type yes when prompted.
  • After the apply completes, query your outputs by running:
terraform output

You should see your instance ID and public IP printed directly in the terminal. No console login required.

Seeing empty or no outputs?

If terraform output prints nothing, make sure you ran terraform apply and confirmed with yes. Outputs are only registered in state after an apply.

If you see an error about outputs.tf, check that the resource name aws_instance.app_server matches exactly what you have in main.tf.

Help me debug why my Terraform outputs are not showing.

Your config is now fully parameterized. Variables control the values, and outputs expose the results. Next up, you will use those variables to modify your live infrastructure and see Terraform's change-management workflow in action.

Modify Infrastructure with the Plan/Apply Cycle

You have a running EC2 instance and a set of reusable variables. Now comes the real payoff of Infrastructure as Code: changing live infrastructure safely.

When you modify a variable, Terraform calculates exactly what must change and shows you a precise diff before touching anything. This is how teams manage production infrastructure without surprises.

In this step, get ready to:
  • Override a variable at the command line and observe Terraform's in-place update plan.
  • Confirm the change and verify it landed using outputs and the AWS console.
  • Inspect the full tracked state with terraform show.
Override a variable and preview the change

The -var flag lets you pass a new value for any variable at runtime without editing your configuration files. Terraform compares the new value against the current state and shows you the difference.

  • Run this command to apply a new instance name:
terraform apply -var="instance_name=terraform-modified"

Terraform will display its plan before asking you to confirm. Before you type anything, look at the plan output.

Do you think Terraform will destroy and recreate the instance, or update it in place? Why?

You should see output indicating an update in-place on aws_instance.app_server. The only change is the Name tag moving from terraform-learn to terraform-modified. Terraform uses a tilde (~) symbol to indicate an in-place update, not a replacement.

Why an update instead of a replacement?

Changing a tag is a metadata-only operation. AWS can update the tag on a running instance without stopping or recreating it. If you changed something fundamental like the AMI or instance type, Terraform would mark the resource for destruction and recreation instead.

Confirm the apply and verify with outputs
  • Type yes at the prompt and press Enter to apply the change.

Terraform updates the tag and confirms with "Apply complete! Resources: 0 added, 1 changed, 0 destroyed."

  • Verify the change landed by running:
terraform output

You should see your instance's ID and public IP printed to the terminal. These values confirm the instance is still running with its state updated.

What does terraform output do?

The terraform output command reads the values you defined in outputs.tf and prints them from the current state. You can query infrastructure details without logging into the AWS console.

  • Open the EC2 console in your browser to confirm the Name tag now reads terraform-modified.

Name tag still showing the old value?

  • Refresh the EC2 console page. AWS sometimes caches the previous tag for a few seconds.
  • Make sure you typed yes at the prompt. If you typed anything else, Terraform cancels the apply.
  • help me debug why my EC2 Name tag did not update after terraform apply
Inspect the tracked state

Terraform keeps a local record of everything it manages in a file called terraform.tfstate. You can inspect this record without opening the file directly.

  • View the full state of your infrastructure by running:
terraform show

You should see the complete attributes of aws_instance.app_server including the AMI, instance type, tags, public IP, and dozens of other values that AWS assigned at creation time.

What does terraform show reveal?

This command prints human-readable output from the state file. It shows every attribute Terraform is tracking for each resource, including read-only values like the instance ARN and availability zone that you never set in your config.

In a team setting, you would use this to quickly answer "what exactly is deployed right now?" without logging into multiple consoles.

Seeing a very long output?

  • This is expected. The terraform show command prints every tracked attribute. Scroll up to find the tags block and confirm it shows Name = "terraform-modified".
  • help me understand what terraform show is printing

You just experienced the core Terraform workflow: change a value, preview the exact diff, confirm, and verify. Next up, you will tear down everything cleanly with a single command.

Destroy Your Infrastructure

You've deployed an instance, refactored it with variables, and modified it safely through the plan/apply cycle. There is one critical skill left: tearing it all down cleanly.

Leaving cloud resources running after you are done costs money. Terraform tracks every resource it created in state, so a single command removes exactly what it provisioned with nothing left behind.

In this step, get ready to:
  • Preview what Terraform will destroy using terraform plan -destroy.
  • Run terraform destroy to terminate the EC2 instance.
  • Verify no running instances remain from this project.
Preview the destruction plan

Before Terraform destroys anything, you can ask it to show you exactly what it plans to remove. This is the same safety mechanism as terraform plan but in reverse.

  • Preview the destruction by running this command:
terraform plan -destroy

What does this output show?

Terraform refreshes its state against AWS, then shows your aws_instance.app_server resource marked with a - prefix. The - means "will be destroyed".

At the bottom you will see a summary line like Plan: 0 to add, 0 to change, 1 to destroy. confirming exactly one resource is targeted for removal.

Destroy the instance

Now that you have seen what will be removed, it is time to execute the destruction for real.

  • Before you run this, do you think Terraform will just delete the instance immediately, or will it ask you something first?
  • Destroy your infrastructure by running this command:
terraform destroy

Terraform shows the same plan you previewed, then prompts you with a confirmation.

  • When you see Do you really want to destroy all resources? followed by Enter a value:, type yes and press Enter.

You will see Terraform log progress lines as it terminates the instance. This can take 30 to 90 seconds. The final line confirms success:

Destroy complete! Resources: 1 destroyed.

What just happened?

Terraform read its state file, identified the one EC2 instance it was tracking, sent a terminate request to AWS, waited for the instance to shut down, then updated terraform.tfstate to record that zero resources now exist.

Your configuration files are untouched. You could run terraform apply again right now and Terraform would recreate the instance from scratch.

Destroy taking a long time or timing out?

EC2 instance termination can take up to 2 minutes. If you see repeated Still destroying... messages, that is normal. Wait for the final confirmation line.

If the command fails with an authentication error, confirm your AWS credentials are still valid by running aws sts get-caller-identity.

Still stuck? Help me troubleshoot why terraform destroy is failing

Verify the instance is gone

Trust but verify. Terraform says the instance is destroyed, but you should confirm independently that nothing is left running in your AWS account.

  • What do you expect this command to return? An empty list, or your old instance?
  • Confirm no running instances remain by running this command:
aws ec2 describe-instances --filters "Name=instance-state-name,Values=running" --query "Reservations[].Instances[].{ID:InstanceId,Name:Tags[?Key=='Name']|[0].Value}" --output table

If Terraform did its job, you will see an empty table or no output at all. Your terraform-modified instance is gone.

Why verify outside of Terraform?

Terraform's state file says the resource is gone, but state can drift from reality. Checking via the AWS CLI gives you independent confirmation that the instance was actually terminated in AWS. In production, this kind of cross-check catches state corruption before it becomes a billing surprise.

Still seeing your instance in the output?

If the instance still shows as running, wait 30 seconds and run the command again. AWS takes a moment to transition the instance to terminated state.

If you see other running instances that are not named terraform-modified or terraform-learn, those belong to a different project and are not from this tutorial.

Need help? Help me verify my EC2 instance was destroyed

Secret mission

Add a Security Group for SSH Access

Your EC2 instance is running, but it's completely unreachable from the outside world. In a real deployment, you'd need to open specific ports so traffic can flow in. Can you define a security group that opens SSH access and attach it to your instance using Terraform's dependency system?

Clean Up Your Resources

Clean Up Your Resources

You already ran terraform destroy in the previous step, so no AWS resources are running. Decide whether to keep your local project files for future reference or remove everything from your machine entirely.

This project runs on the AWS Free Tier, so there are no ongoing costs as long as cloud resources are destroyed.

Resources you used:

  • EC2 instance (t2.micro, already destroyed via terraform destroy).
  • Security Group (already destroyed via terraform destroy).
  • Local project directory with Terraform configuration files and state.
  • 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 or want to keep the project files as a reference.

  • Your local project directory remains on your Desktop with all .tf files intact.
  • Terraform and AWS CLI stay installed and available for future projects.
  • Your AWS credentials remain configured in ~/.aws/. These are useful for any AWS project.
  • No AWS resources are running, so you will not incur any charges.

Pause - I'll come back to this later

Since you already destroyed all cloud resources, there is nothing to pause. Your local files are safe to leave in place until you return.

  • Your project directory stays on your Desktop for when you want to resume.
  • To redeploy later, navigate back into the project directory and run terraform apply.
  • No charges will accrue while cloud resources remain destroyed.

Delete - I don't want to use this again

Remove all project resources and tools from your machine. Follow each step below.

  • Delete the project directory by running:
cd ~/Desktop
rm -rf terraform-learn
  • Uninstall Terraform by running:
sudo apt remove terraform
  • Uninstall the AWS CLI by running:
sudo rm /usr/local/bin/aws
sudo rm /usr/local/bin/aws_completer
sudo rm -rf /usr/local/aws-cli
  • Remove your stored AWS credentials by running:
rm -rf ~/.aws/

Removing credentials is permanent

The ~/.aws/ folder stores credentials and configuration shared across all AWS tools and SDKs. Only remove it if you have no other projects that depend on these credentials.

That's a Wrap!

That's a Wrap!

Nice work! You just deployed a real EC2 instance on AWS using nothing but code, then tore it down just as cleanly.

You've learned how to:

  • Write Terraform configuration files from scratch (provider, resources, variables, outputs) and deploy a real EC2 instance to AWS with terraform apply.
  • Modify live infrastructure safely using the plan/apply workflow, seeing exactly what Terraform will change before it touches anything.
  • Manage the full Terraform lifecycle (init, plan, apply, destroy) and clean up all resources with a single terraform destroy command.
  • Secret Mission: Added a security group with SSH access, learning how Terraform manages resource dependencies between infrastructure components.

Ready to quiz yourself?