Ravindra BagaleCourses & study guides मराठी

Chapter 40: Terraform Real AWS Labs VPC EC2 nginx S3 and IAM Role

40.5 Lab part 5 — a private S3 bucket, then destroy everything

Story. An Instagram-style team stores static assets in S3 and must never make the bucket public by accident. The bucket gets public access fully blocked, encryption on, and force_destroy only in the lab so destroy can empty it. Then comes the most important step of every lab: terraform destroy, and confirm nothing is left running to avoid cost.

S3 bucket public blocked upload test object EC2 reads via role destroy everything
Create a private bucket, test it from the instance through the role, then destroy the whole lab.
resource "aws_s3_bucket" "assets" {
  bucket        = "badge-lab-assets-<your-account-id>"
  force_destroy = true
}

resource "aws_s3_bucket_public_access_block" "assets" {
  bucket                  = aws_s3_bucket.assets.id
  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

resource "aws_s3_object" "hello" {
  bucket  = aws_s3_bucket.assets.id
  key     = "hello.txt"
  content = "served through the instance role"
}

# from a Session Manager shell on the instance
aws s3 cp s3://badge-lab-assets-<your-account-id>/hello.txt -

# back on your machine
terraform destroy

What each line does

  • bucket = "badge-lab-assets-<your-account-id>" — Unique name; replace the placeholder.
  • force_destroy = true — Lets destroy delete a bucket that still has objects; lab only.
  • block_public_acls = true (and the three lines after) — Blocks all public access.
  • resource "aws_s3_object" "hello" { — Uploads one small test object.
  • content = "served through the instance role" — The object body.
  • aws s3 cp s3://.../hello.txt - — Reads the object on the server using the role; no keys configured.
  • terraform destroy — Deletes every lab resource to avoid cost.

When it is green.

served through the instance role

Plan: 0 to add, 0 to change, 18 to destroy.
Destroy complete! Resources: 18 destroyed.

When it is red.

download failed: s3://badge-lab-assets-.../hello.txt to - An error occurred (AccessDenied)
when calling the GetObject operation: Access Denied

Fix. The role policy resource ARN is wrong, or the instance profile is missing. Check part 4 and remember the /* at the end of the bucket ARN.

What: every term in plain English

  • public access block — Four settings that stop any ACL or policy from making the bucket public.
  • server-side encryption — S3 encrypts every object on disk. SSE-S3 is free; SSE-KMS uses a KMS key.
  • force_destroy — Lets terraform destroy delete a bucket that still has objects. Use only in labs.
  • aws_s3_object — A Terraform resource that uploads one file to a bucket.
  • etag — A hash of the object. Terraform uses filemd5 to notice when the file changes.
  • destroy plan — terraform plan -destroy shows what will be deleted before you delete it.
  • leftover check — Commands run after destroy to prove nothing is still billing.

Example 1: Upload a test object with Terraform

A Nykaa team ships its config files to S3 through Terraform, so every change is reviewed. Do the same with one small file.

  1. Create hello.txt with printf.
  2. Add an aws_s3_object resource.
  3. Apply.
resource "aws_s3_object" "hello" {
  bucket = aws_s3_bucket.assets.id
  key    = "hello.txt"
  source = "${path.module}/hello.txt"
  etag   = filemd5("${path.module}/hello.txt")
}
printf 'Hello from the badge lab\n' > hello.txt
terraform apply -auto-approve
aws s3 ls s3://badge-lab-assets-123456789012/

Output

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
2026-10-25 12:20:44         25 hello.txt

What it shows: Change hello.txt and plan again; the etag changes, so Terraform uploads the new version.

Example 2: Prove the bucket is private

Every quarter, auditors at a Bajaj Finserv vendor try the public URL. Do it yourself first.

  1. Try the public object URL with curl.
  2. Read the public access block.
curl -s -o /dev/null -w "%{http_code}\n" https://badge-lab-assets-123456789012.s3.ap-south-1.amazonaws.com/hello.txt
aws s3api get-public-access-block --bucket badge-lab-assets-123456789012 --output text

Output

403
PUBLICACCESSBLOCKCONFIGURATION  True    True    True    True

What it shows: 403 from outside and all four blocks True. Only the role can read it.

Example 3: Read the destroy plan before destroying

At a Jio project, a destroy ran in the wrong folder and removed a shared VPC. Reading the destroy plan takes ten seconds and prevents that.

  1. Run terraform workspace show and pwd.
  2. Run plan -destroy.
  3. Count the resources.
pwd; terraform workspace show
terraform plan -destroy | tail -1
terraform destroy -auto-approve | tail -1

Output

/home/ravi/aws-lab
default
Plan: 0 to add, 0 to change, 17 to destroy.
Destroy complete! Resources: 17 destroyed.

What it shows: The count matches what you built across the five parts. Right folder, right workspace, then destroy.

Example 4: Check nothing is left running

A forgotten instance from a weekend lab is the most common surprise bill. Run three checks after every destroy.

  1. List lab instances.
  2. List lab buckets.
  3. Check state is empty.
aws ec2 describe-instances --filters Name=tag:Project,Values=badge-lab Name=instance-state-name,Values=running \
  --query "length(Reservations)"
aws s3 ls | grep badge-lab || echo "no lab buckets"
terraform state list | wc -l

Output

0
no lab buckets
0

What it shows: Zero running, zero buckets, empty state. The default_tags from part 1 make this check easy.

Command cheat sheet

Command What it does When to use it
terraform plan -​destroy Shows what destroy will delete Before every destroy
terraform destroy Deletes everything in state End of every lab
aws s3api get-​public-​access-​block --​bucket B Shows the four blocks Audit privacy
aws s3 ls s3://​B/ Lists objects Check uploads
aws ec2 describe-​instances --​filters Name=​tag:​Project,​Values=​badge-​lab Finds lab servers Leftover check
terraform state list | wc -​l Counts managed resources Prove empty state

Verify: did it work?

  1. Bucket is private

    curl -s -o /dev/null -w "%{http_code}" https://badge-lab-assets-123456789012.s3.ap-south-1.amazonaws.com/hello.txt
    

    Output

    403
    
  2. State empty after destroy

    terraform state list | wc -l
    

    Output

    0
    

When it is red 1

Error: deleting S3 Bucket: BucketNotEmpty: The bucket you tried to delete is not empty

Cause. Objects remain and force_destroy is false.

Fix, step by step

  1. Set force_destroy = true for the lab and apply.
  2. Run destroy again.
  3. Or empty the bucket with aws s3 rm --recursive.

When it is red 2

Error: creating S3 Bucket: BucketAlreadyExists

Cause. Bucket names are global and someone owns that name.

Fix, step by step

  1. Add your account id to the name.
  2. Plan again.

When it is red 3

Error: deleting EC2 VPC: DependencyViolation

Cause. Something made by hand is still in the VPC, such as an ENI or a security group.

Fix, step by step

  1. Find it with describe-network-interfaces filtered by VPC.
  2. Delete it in the console.
  3. Run destroy again.

Cleanup

terraform destroy -auto-approve
rm -f hello.txt

Ravindra Bagale's Tip

End every lab with three leftover checks: instances, buckets, state. Make it a habit and you will never get a surprise bill.

Practice tasks

Easy Upload

Why: Use aws_s3_object.

What to do: Upload hello.txt with Terraform.

How:
  1. Use filemd5 for etag.

Check: hello.txt listed.

Easy Private

Why: Audit it.

What to do: Curl the public URL.

How:
  1. Expect 403.

Check: 403.

Medium Destroy plan

Why: Avoid accidents.

What to do: Run plan -destroy and read the count.

How:
  1. Check pwd first.

Check: 17 to destroy.

Medium Leftovers

Why: Avoid cost.

What to do: Run the three checks after destroy.

How:
  1. Use the Project tag.

Check: 0, no lab buckets, 0.

Easy Say it in two lines

Why: If you can say it simply, you understand it.

What to do: Write two short lines about this lesson for a fresher.

How:
  1. Write the problem it solves.
  2. Write the one command or file you use.
  3. Read it aloud.

Check: A fresher understands it without a question.

Easy Start a safe lab folder

Why: Practice needs a throwaway place.

What to do: Make a new folder with versions.tf and a random_pet resource, so no AWS cost.

How:
  1. mkdir tf-lab and cd into it.
  2. Add the hashicorp/random provider and one random_pet.
  3. Run terraform init and terraform apply.

Check: Apply complete! with 1 added.

Medium Break it on purpose

Why: You must know the red output by face.

What to do: Cause one error from the red part of this lesson and fix it.

How:
  1. Follow one red section.
  2. Copy the exact message to your notes.
  3. Write the fix in one line.

Check: You fixed it without searching the web.

Medium Run it twice

Why: A second run must be quiet.

What to do: Run the same plan two times with no edits.

How:
  1. Run terraform plan.
  2. Run it again.
  3. Explain any change it still shows.

Check: The second plan says No changes, or you can explain each line.

Medium Write a runbook page

Why: At 2 a.m. nobody remembers flags.

What to do: Write a one-page runbook for this lesson.

How:
  1. Title, when to use, exact commands, how to check, how to undo.

Check: A teammate can follow it alone.

Medium Run it from a pipeline

Why: Real teams run Terraform from CI, not from laptops.

What to do: Run the main command of this lesson inside a GitHub Actions or Jenkins job.

How:
  1. Add a job with hashicorp/setup-terraform or a Terraform Docker image.
  2. Give it AWS access through an IAM role, never a key in the file.
  3. Run init, then the command from this lesson.

Check: The job log shows the same output you saw on your laptop.

Medium Check the cost

Why: Labs left running cost real money.

What to do: Find what this lesson would cost per day if you forgot to destroy it.

How:
  1. Open the AWS Pricing Calculator.
  2. Add each resource from your plan.
  3. Write the daily cost in your notes.

Check: You know the daily cost and you ran terraform destroy.

Hard Draw the flow top to bottom

Why: A drawing shows the order of work.

What to do: Draw this lesson as boxes from top to bottom.

How:
  1. Start at your code, go through plan and state, end at AWS.
  2. Mark where each command reads or writes.

Check: Each box matches a real step you ran.

Hard Explain it like an interview

Why: Interviewers want real stories.

What to do: Record a 60 second answer about this lesson.

How:
  1. Say the problem, the command, and one error you hit.

Check: Your answer has a real command and a real error message.

Homework: try at home

Try at home

Write a destroy checklist for your team with pwd, workspace, plan -destroy, destroy and the three leftover checks.

Recap

  • Block public access and encrypt every bucket.
  • force_destroy only in labs.
  • Upload files with aws_s3_object and filemd5.
  • Read plan -destroy, then destroy.
  • Prove nothing is left with three checks.

FAQ

Is force_destroy safe in prod?

No. It deletes all objects. Keep it false outside labs.

Is SSE-S3 enough?

For most labs and many apps, yes. Use KMS when you need key control or audit.

Why 403 and not 404?

S3 hides whether the object exists from callers who are not allowed.

Can I destroy one part only?

Use -target only for emergencies. For labs, destroy all.

What costs money in this lab?

The EC2 instance and its public IPv4 address. The rest is almost free.

Interview questions

  1. How do you keep an S3 bucket private with Terraform?

    aws_s3_bucket_public_access_block with all four settings true, plus encryption.

  2. What is force_destroy?

    Allows deleting a non-empty bucket on destroy.

  3. How do you upload a file with Terraform?

    aws_s3_object with source and etag = filemd5.

  4. How do you avoid destroying the wrong thing?

    Check folder and workspace, read plan -destroy.

  5. How do you confirm nothing is left after a lab?

    Check instances, buckets and an empty state list.

Quick quiz

Q1. HTTP code for a private object?

403

Q2. Setting to delete non-empty buckets?

force_destroy

Q3. Command to preview destroy?

terraform plan -destroy

Q4. Function used for etag?

filemd5

Q5. Empty state count after destroy?

0

Lab

  1. Create versions.tf with the AWS provider ~> 5.0 and your region.
  2. Add parts 1 to 5 as vpc.tf, sg.tf, ec2.tf, iam.tf and s3.tf, plus the aws_ami data source.
  3. Run terraform init, fmt and validate.
  4. Run terraform plan and confirm the add count.
  5. Run terraform apply and open web_url in a browser.
  6. Open Session Manager on the instance and read hello.txt.
  7. Run terraform destroy and confirm Destroy complete! to avoid cost.

Practice task

Add a second instance in subnet b using for_each over the subnets, and describe the plan you expect.

Ravindra Bagale's Tip

Write terraform destroy on a sticky note before you type apply.

In short

  • A public subnet needs an internet gateway and a 0.0.0.0/0 route.
  • No SSH: HTTP only, Session Manager through the role.
  • user_data configures the server at boot; IMDSv2 is required.
  • Roles give servers temporary credentials; no keys on disk.
  • Private buckets block public access; destroy at the end.

Samajla ka?

Ravi Sir's Cards for this lesson

मराठी cards