Skip to main content
Terraform Basics
CHAPTER 09

Terraform with AWS

Updated: May 15, 2026
30 min read

# CHAPTER 9

Terraform with AWS

1. Introduction

Amazon Web Services (AWS) is the dominant force in cloud computing, and the AWS Provider is the most heavily utilized plugin in the Terraform ecosystem. While Terraform is cloud-agnostic, the code you write is cloud-specific. You cannot use an aws_instance block to build a server in Microsoft Azure. Therefore, mastering Terraform requires mastering the specific syntax and resource relationships of your chosen cloud provider. In this chapter, we will focus exclusively on AWS, provisioning core infrastructure including IAM users, EC2 virtual machines, and S3 storage buckets.

2. Learning Objectives

By the end of this chapter, you will be able to:
  • Authenticate the AWS Provider securely.
  • Provision an S3 Bucket and configure security policies.
  • Create an IAM User and assign permission policies.
  • Provision an EC2 instance using a dynamic Data Source.
  • Understand the relationship between Security Groups and EC2.

3. Beginner-Friendly Explanation

Imagine AWS as a massive, heavily guarded hotel.
  • IAM (Identity and Access Management): The hotel security desk. It gives you a keycard and determines if you are allowed to enter the gym or only your room.
  • S3 (Simple Storage Service): A secure locker in the basement where you can throw millions of files, pictures, and documents.
  • EC2 (Elastic Compute Cloud): A hotel room with a computer in it that you can rent by the minute.
  • Security Group: The bouncer standing directly outside your specific hotel room door, deciding who is allowed to knock.

4. Provisioning S3 (Storage)

Let's look at the HCL required to build a private S3 bucket.
hcl
12345678910111213
resource "aws_s3_bucket" "company_documents" {
  bucket = "my-unique-company-docs-bucket-9988" # Must be globally unique across ALL of AWS!
}

# In AWS Provider v4+, public access blocking is a separate resource
resource "aws_s3_bucket_public_access_block" "block_public" {
  bucket = aws_s3_bucket.company_documents.id # Implicit dependency!

  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

5. Provisioning IAM (Security)

Creating a user and giving them "Read Only" access.
hcl
123456789
resource "aws_iam_user" "developer" {
  name = "junior-dev-alice"
}

# Attach an existing AWS-managed policy to the user
resource "aws_iam_user_policy_attachment" "read_only" {
  user       = aws_iam_user.developer.name
  policy_arn = "arn:aws:iam::aws:policy/ReadOnlyAccess"
}

6. Mini Project: Deploy AWS Infrastructure

Let's build a secure Web Server. We need a Security Group (Firewall) to allow web traffic (Port 80) and an EC2 instance. We will use a Data Source to dynamically find the latest Ubuntu Operating System image, rather than hardcoding an ID that will expire next month.

Step-by-Step Architecture Concept:

hcl
12345678910111213141516171819202122232425262728293031323334353637383940414243444546
# 1. DATA SOURCE: Dynamically search AWS for the latest Ubuntu OS ID
data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"] # Canonical's official AWS Account ID

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
  }
}

# 2. SECURITY GROUP: The Firewall
resource "aws_security_group" "web_traffic" {
  name        = "allow_web"
  description = "Allow inbound HTTP traffic"

  # Inbound Rules (Ingress)
  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"] # Allow from anywhere
  }

  # Outbound Rules (Egress)
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1" # All protocols
    cidr_blocks = ["0.0.0.0/0"]
  }
}

# 3. EC2 INSTANCE: The Server
resource "aws_instance" "web_server" {
  # Use the ID found by the Data Source above!
  ami                    = data.aws_ami.ubuntu.id 
  instance_type          = "t2.micro"
  
  # Attach the Firewall!
  vpc_security_group_ids = [aws_security_group.web_traffic.id] 
  
  tags = {
    Name = "My-Web-Server"
  }
}

7. Real-World Scenarios

A company launched a new web application. They hardcoded the ami (Operating System ID) in their Terraform code to a specific Amazon Linux image. Six months later, they tried to scale up and run terraform apply to create 10 more servers. Terraform failed. AWS had deprecated and removed that specific AMI ID because it was too old and contained vulnerabilities. The DevOps team had to rewrite the code during an outage. They replaced the hardcoded string with an awsami Data Source, ensuring Terraform would dynamically fetch the newest, most secure OS version every time it ran, permanently solving the problem.

8. Best Practices

  • Data Sources over Hardcoding: As demonstrated, a data block is the opposite of a resource block. A resource *creates* something. A data block *reads* something that already exists in the cloud. Always use Data Sources to dynamically fetch IDs (like VPC IDs, Subnet IDs, or AMIs) rather than hardcoding magic strings into your code.

9. Security Recommendations

  • 0.0.0.0/0 (The Danger Zone): In our Security Group example, we allowed 0.0.0.0/0 (The entire public internet) to access Port 80. This is fine for a public web server. NEVER use 0.0.0.0/0 for Port 22 (SSH) or Port 3306 (MySQL). Databases and administrative ports must be strictly restricted to your company's specific IP addresses, or placed in private subnets.

10. Troubleshooting Tips

  • Eventual Consistency: Sometimes you run terraform apply to create an IAM Role, and instantly try to attach it to an EC2 instance in the next block. AWS might throw an error saying "Role not found." Cloud APIs take a few seconds to sync globally. If this happens, you may need to add a small sleep timer or rerun the apply.

11. Exercises

  1. 1. What is the fundamental difference between a resource block and a data block in Terraform?
  1. 2. In the AWS Provider, why must the awss3bucket name be globally unique, not just unique within your own account?

12. FAQs

Q: Do I have to define the VPC (Network) every time I make an EC2 instance? A: No. If you omit the networking arguments (like subnet
id), the AWS Provider will automatically place your EC2 instance inside your account's "Default VPC."

13. Interview Questions

  • Q: Explain the operational advantage of utilizing an awsami data source with dynamic filtering rather than hardcoding an AMI ID string in an awsinstance resource block.
  • Q: Describe the HCL structure required to provision an AWS EC2 instance and securely attach an explicitly defined Security Group. How does Terraform handle the dependency between these two resources?

14. Summary

In Chapter 9, we applied our Terraform knowledge to the world's leading cloud platform. We explored the syntax required to provision foundational AWS resources: secure S3 storage, identity and access management (IAM) policies, and virtual machines (EC2). We mastered the critical concept of data sources, learning how to dynamically query the AWS API for real-time information to prevent our code from becoming brittle and outdated. Finally, we demonstrated how to weave these distinct resources together, dynamically linking firewalls to servers to create a cohesive, automated cloud architecture.

15. Next Chapter Recommendation

AWS is massive, but it isn't the only cloud. What if your company decides to use Microsoft or Google? How different is the code? Proceed to Chapter 10: Terraform with Azure and GCP.

Finish this Chapter

Save your progress on your learning path and prepare for coding interview challenges.

Discussion

Join the discussion

Log in or create a free account to participate.

Sort: ·