Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

My Second DevOps Project — Terraform on AWS

Provisioning a Linux server on AWS using Terraform. Instead of clicking through the AWS Console to create a server, this Terraform config does it all automatically in under 60 seconds.

What it does:

  • Creates an SSH key pair on AWS (so you can log in)
  • Creates a security group (firewall allowing SSH + HTTP)
  • Spins up a t3.micro EC2 instance (Ubuntu 24.04 Free Tier)
  • Prints the server's IP and a ready-to-run SSH command

Tech stack: Terraform · AWS EC2 · Ubuntu Linux

Project Structure

terraform-project-2/
├── README.md          ← You are here
├── main.tf            ← The main infrastructure (provider, key, security group, EC2)
├── variables.tf       ← Input variables (region, AMI, instance type, key path)
├── outputs.tf         ← Values printed after apply (IP address, SSH command)
├── .gitignore         ← Keeps state files and secrets out of GitHub
└── alternate/
    └── main-modern-sg-style.tf.example   ← Reference-only alt. security group pattern

Prerequisites

Before you start, you need three things:

1. An AWS Account

Sign up at aws.amazon.com if you don't have one. Everything in this project is free tier eligible if you terminate the instance when done.

2. Terraform Installed

Mac:

$ brew tap hashicorp/tap
$ brew install hashicorp/tap/terraform
$ terraform -v   # should print a version number

3. AWS CLI Installed and Configured

Mac:

$ brew install awscli

Then configure it with your AWS credentials:

$ aws configure

You'll be prompted for:

AWS Access Key ID:     ← from AWS Console → IAM → Users → Security credentials → Create access key
AWS Secret Access Key: ← same place
Default region name:   us-east-1 (pick based on your zone)
Default output format: json

To create an access key: AWS Console → IAMUsers → your username → Security credentials tab → Create access key → choose "CLI" → download the CSV.


Setup Guide

PART 1 — Generate an SSH Key (skip if you already have one)

Check if you already have a key:

$ ls ~/.ssh/id_rsa.pub

If you see the file, skip to Part 2.

If not, create one:

$ ssh-keygen -t rsa -b 4096 -C "your@email.com"
# Press Enter through all prompts to use the default location (~/.ssh/id_rsa)

This creates two files:

  • ~/.ssh/id_rsa — your private key (never share this)
  • ~/.ssh/id_rsa.pub — your public key (this gets uploaded to AWS)

PART 2 — Clone This Repo

$ git clone https://github.com/bysaania/terraform-project-2.git
$ cd terraform-project-2

PART 3 — Initialize Terraform and validate

$ terraform init

This downloads the AWS provider plugin (a binary that lets Terraform talk to AWS). You only need to run this once per project.

Expected output:

% terraform init
Initializing provider plugins found in the configuration...
- Finding hashicorp/aws versions matching "~> 6.0"...
- Installing hashicorp/aws v6.51.0...
- Installed hashicorp/aws v6.51.0 (signed by HashiCorp)

Initializing the backend...


Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

Terraform has been successfully initialized!

To make sure that all your .tf files are neatly formatted with proper indentation

$ terraform fmt

Then run the following to validate before you run plan

$ terraform validate
Success! The configuration is valid.

PART 4 — Preview What Will Be Created

$ terraform plan

This is a dry run — it shows you exactly what Terraform would create without actually doing anything. Read through the output before applying.

Expected output (abbreviated):


$ terraform plan

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_instance.web will be created
  + resource "aws_instance" "web" {
  + ami                                  = "ami-0b6d9d3d33ba97d99"..
  + instance_type                        = "t3.micro"
  + key_name                             = "terraform-project-2-key"   
  + region                               = "us-east-1"
  ...
  + tags                                 = {
          + "Name"    = "terraform-project-2"
          + "Project" = "terraform-project-2"
    }
 }

    # aws_key_pair.terraform_key will be created
    + resource "aws_key_pair" "terraform_key" {
      + key_name        = "terraform-project-2-key"
      + public_key      = "ssh-rsa ....l5lQyhWu05OioNy.......== terraform-project-2"
      + region          = "us-east-1"
      + tags            = {
          + "Name"    = "terraform-project-2-key"
          + "Project" = "terraform-project-2"
        }
}


  # aws_security_group.terraform_sg will be created
  + resource "aws_security_group" "terraform_sg" {
      + name = "terraform-project-2-sg"
      ...
    }

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

Changes to Outputs:
  + instance_id        = (known after apply)
  + instance_public_ip = (known after apply)
  + ssh_command        = (known after apply)
  + web_url            = (known after apply)


Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.

The note about -out is worth understanding since it's a genuinely useful practice, not just a leftover message.

For a learning project run interactively in one sitting, this isn't critical. This is shown in PART 5 below.


PART 5 — Apply (Create the Infrastructure)

$ terraform apply

Terraform will show the plan again and ask for confirmation. Type yes and press Enter.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

You will see something like this:

aws_key_pair.terraform_key: Creating...
aws_security_group.terraform_sg: Creating...
aws_key_pair.terraform_key: Creation complete after 1s [id=terraform-project-2-key]
aws_security_group.terraform_sg: Creation complete after 4s [id=sg-062b8c0ec3566eea0]
aws_instance.web: Creating...

Wait ~30 seconds. When it finishes, you'll see:

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.


Outputs:

instance_id = "i-0601039e51bf10ed4"
instance_public_ip = "98.88.26.133"
ssh_command = "ssh -i ~/.ssh/id_rsa ubuntu@98.88.26.133"
web_url = "http://98.88.26.133"

Alternate way to run apply. No re-evaluation, no chance that something in AWS changed between your plan and apply commands and silently altered what gets created.

It becomes important in CI/CD pipelines, where plan and apply might run as separate automated steps minutes or hours apart, and you want a guarantee that what gets approved in plan is exactly what gets applied

$ terraform plan -out=tfplan
$ terraform apply tfplan

This saves the exact plan to a file, and apply then executes precisely that saved plan


PART 6 — Verify: SSH Into Your Server

Copy the ssh_command from the output and run it:

$ ssh -i ~/.ssh/id_rsa ubuntu@54.123.45.67

You should land inside your AWS server:

Welcome to Ubuntu 24.04 LTS (GNU/Linux 6.8.0-1016-aws x86_64)

ubuntu@ip-172-31-23-172:~$

You're in. Type exit to leave.


PART 7 — Retrieve Outputs Anytime

If you need the IP or SSH command later without running apply again:

$ terraform output

PART 8 — Run It Again (Idempotency)

$ terraform apply

Run apply a second time. Notice:

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

0 changed — Terraform checked the real state of AWS, compared it to your .tf files, and confirmed everything already matches. Nothing to do.

This is called idempotency — the same concept from Ansible's changed=0. Your infrastructure is always in the exact state your code describes.


PART 9 — Destroy (Clean Up)

When you're done, delete everything with one command:

$ terraform destroy

Type yes when prompted.

Destroy complete! Resources: 3 destroyed.

The EC2 instance, security group, and key pair are all gone. No clicking in the AWS Console required.

Always destroy when done. Even though t3.micro is free tier, it's good practice. A stopped instance can still incur EBS storage costs.


Key Concepts Explained

What is Terraform?

Terraform is an Infrastructure as Code tool. Instead of clicking through a web console to create servers, you write code that describes what you want, and Terraform creates it. This makes infrastructure:

  • Repeatable — run it 10 times, get the same result
  • Version controlled — your infrastructure lives in Git like any other code
  • Destroyableterraform destroy removes everything cleanly

Terraform vs Ansible

Terraform Ansible
What it does Creates infrastructure (servers, networks, storage) Configures existing servers (installs software, sets up users)
Mental model "Make this server exist" "Make this server look like this"
Language HCL (HashiCorp Configuration Language) YAML
Runs against Cloud APIs (AWS, GCP, Azure) SSH into servers
Project A used
Project B uses
Project C will use

What is State?

When you run terraform apply, Terraform saves a record of what it created in a file called terraform.tfstate. This is how Terraform knows what already exists on your next run.

Never delete terraform.tfstate — Terraform needs it to track what's in AWS. Never commit terraform.tfstate to GitHub — it's in .gitignore for this reason.

The 4 Terraform Commands You'll Use 90% of the Time

Command What it does
terraform init Downloads providers. Run once per project.
terraform plan Dry run — shows what would change. Always run before apply.
terraform apply Creates/updates infrastructure to match your .tf files.
terraform destroy Deletes everything Terraform created.

Troubleshooting

"Error: No valid credential sources found" Your AWS credentials aren't configured. Run aws configure and enter your access key and secret.

"Error: InvalidAMIID.NotFound" The AMI ID in variables.tf is region-specific. If you changed aws_region, update ami_id to match the Ubuntu 24.04 AMI in your chosen region.

SSH "Permission denied (publickey)" Make sure your private key has correct permissions:

chmod 400 ~/.ssh/id_rsa

SSH "Connection timed out" Wait 60–90 seconds after terraform apply — EC2 instances take a moment to fully boot.


Push to GitHub

$ git init
$ git add .
$ git commit -m "Project 2: Terraform EC2 instance with security group and SSH key pair"
$ git remote add origin https://github.com/bysaania/terraform-project-2.git
$ git push -u origin main


Alternate Security Group Pattern

This project uses the classic security group style — ingress and egress rules nested directly inside the aws_security_group resource block. It's the clearest way to learn what a security group actually is.

There's also a modern style that HashiCorp now recommends, where each rule is its own separate resource (aws_vpc_security_group_ingress_rule, aws_vpc_security_group_egress_rule) linked back to the security group by ID. This pattern is more common in production codebases because each rule can be added, removed, or changed independently without touching the whole security group.

A fully commented reference version is included at:

alternate/main-modern-sg-style.tf.example

This file is intentionally not loaded by Terraform. The .tf.example extension means terraform init/apply will never read it, so it can't conflict with the real main.tf — even though both files define resources with the same names. If you want to actually run the modern version, copy it into its own separate project folder (with its own variables.tf and outputs.tf), rename it to main.tf, and run Terraform from there.

Classic (used in main.tf) Modern (reference only)
Rules location Nested inside the security group Separate resources
Good for Learning, small projects Production, many rules
Resource count 1 1 + 1 per rule
File main.tf alternate/main-modern-sg-style.tf.example

Next Steps

  • Project C: Use Terraform to create the EC2 instance, then Ansible to configure it — the full Infrastructure as Code story

Part of my cloud/DevOps learning journey. Built as a beginner — intentionally simple. linkedin.com/in/saania-khanna

About

Terraform project that provisions an EC2 instance, security group, and SSH key pair on AWS

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages