Terraform on AWS -- Complete Guide to Provisioning AWS Infrastructure
Terraform on AWS lets you provision the full stack of AWS services -- compute, storage, networking, databases, and serverless -- using declarative HCL configuration files version-controlled in Git.
What You'll Learn
In this tutorial, you will learn how to configure the AWS provider, provision EC2 instances with security groups, create VPCs with subnets, manage S3 buckets with policies, deploy RDS databases, set up IAM roles, and follow security best practices for production AWS infrastructure.
Why It Matters
AWS offers over 200 services, and managing them through the console or CLI scripts does not scale. Terraform provides a single declarative language for all AWS resources, with plan preview, state tracking, and automated deployment through CI/CD pipelines.
Real-World Use
DodaTech runs its entire infrastructure on AWS using Terraform. Durga Antivirus Pro's backend uses EC2 auto-scaling groups, RDS Multi-AZ databases, S3 data lakes, and Lambda functions for malware analysis -- all defined in Terraform and deployed through GitHub Actions.
AWS Provider Configuration
graph TD
A[Terraform AWS Config] --> B[AWS Provider]
B --> C[EC2]
B --> D[VPC]
B --> E[S3]
B --> F[RDS]
B --> G[IAM]
B --> H[Lambda]
C --> I[Instances]
C --> J[Security Groups]
C --> K[Auto Scaling]
D --> L[Subnets]
D --> M[Route Tables]
D --> N[Internet Gateway]
style A fill:#ff9900,color:#fff
style B fill:#ff9900,color:#fff
Provider Setup
# provider.tf
terraform {
required_version = ">= 1.6"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "us-east-1"
assume_role {
role_arn = "arn:aws:iam::123456789012:role/TerraformDeployer"
session_name = "TerraformSession"
}
default_tags {
tags = {
Environment = var.environment
ManagedBy = "Terraform"
Project = var.project_name
}
}
}
terraform init
Expected output:
Initializing the backend...
Initializing provider plugins...
- Finding hashicorp/aws versions matching "~> 5.0"...
- Installing hashicorp/aws v5.72.0...
- Installed hashicorp/aws v5.72.0 (signed by HashiCorp)
EC2 Instance with Security Group
# ec2.tf
resource "aws_security_group" "web" {
name = "${var.environment}-web-sg"
description = "Security group for web servers"
vpc_id = data.aws_vpc.default.id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 443
to_port = 443
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 = "${var.environment}-web-sg" }
}
resource "aws_instance" "web" {
ami = data.aws_ami.amazon_linux_2.id
instance_type = var.instance_type
subnet_id = data.aws_subnet.public.id
vpc_security_group_ids = [aws_security_group.web.id]
user_data = <<-EOF
#!/bin/bash
dnf install -y nginx
systemctl enable --now nginx
echo "Hello from Terraform" > /usr/share/nginx/html/index.html
EOF
tags = {
Name = "${var.environment}-web-server"
}
}
terraform plan
Expected output:
Terraform will perform the following actions:
# aws_instance.web will be created
+ resource "aws_instance" "web" {
+ ami = "ami-0abcdef1234567890"
+ instance_type = "t3.medium"
+ vpc_security_group_ids = (known after apply)
+ subnet_id = "subnet-0123456789abcdef0"
+ tags = { "Name" = "production-web-server" }
}
Plan: 2 to add, 0 to change, 0 to destroy.
VPC with Public and Private Subnets
# vpc.tf
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = { Name = "${var.environment}-vpc" }
}
resource "aws_subnet" "public" {
count = length(var.public_subnet_cidrs)
vpc_id = aws_vpc.main.id
cidr_block = var.public_subnet_cidrs[count.index]
availability_zone = var.availability_zones[count.index]
map_public_ip_on_launch = true
tags = { Name = "${var.environment}-public-${count.index + 1}" }
}
resource "aws_subnet" "private" {
count = length(var.private_subnet_cidrs)
vpc_id = aws_vpc.main.id
cidr_block = var.private_subnet_cidrs[count.index]
availability_zone = var.availability_zones[count.index]
tags = { Name = "${var.environment}-private-${count.index + 1}" }
}
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = { Name = "${var.environment}-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.main.id
}
tags = { Name = "${var.environment}-public-rt" }
}
resource "aws_route_table_association" "public" {
count = length(aws_subnet.public)
subnet_id = aws_subnet.public[count.index].id
route_table_id = aws_route_table.public.id
}
RDS Multi-AZ Database
# rds.tf
resource "aws_db_instance" "main" {
identifier = "${var.environment}-database"
engine = "postgres"
engine_version = "16.3"
instance_class = var.db_instance_class
allocated_storage = var.db_storage_gb
storage_encrypted = true
storage_type = "gp3"
multi_az = var.environment == "production" ? true : false
db_name = var.db_name
username = var.db_username
password = random_password.master.result
vpc_security_group_ids = [aws_security_group.database.id]
db_subnet_group_name = aws_db_subnet_group.main.name
backup_retention_period = var.environment == "production" ? 30 : 7
backup_window = "03:00-04:00"
maintenance_window = "sun:04:00-sun:05:00"
deletion_protection = var.environment == "production" ? true : false
skip_final_snapshot = var.environment == "production" ? false : true
}
AWS IAM with Terraform
# iam.tf
resource "aws_iam_role" "lambda_exec" {
name = "${var.environment}-lambda-exec"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole]
Effect = "Allow"
Principal = {
Service = "lambda.amazonaws.com"
}
}]
})
}
resource "aws_iam_role_policy_attachment" "lambda_basic" {
role = aws_iam_role.lambda_exec.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}
resource "aws_iam_role_policy" "s3_access" {
name = "s3-read-write"
role = aws_iam_role.lambda_exec.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow]
Action = [
"s3:GetObject",
"s3:PutObject",
"s3:ListBucket]
]
Resource = [
aws_s3_bucket.data.arn,
"${aws_s3_bucket.data.arn}/*]
]
}]
})
}
Common Mistakes
1. Hardcoding AMI IDs
AMI IDs change per region. Use data.aws_ami data sources to look up the latest AMI dynamically.
2. Leaving Security Groups Too Permissive
Opening all ports to 0.0.0.0/0 creates security risks. Restrict ingress to specific CIDRs and ports.
3. Skipping Encryption
Unencrypted S3 buckets, EBS volumes, and RDS instances fail compliance audits. Enable encryption by default.
4. Not Using IAM Roles
Hardcoding AWS access keys in configuration is insecure. Use IAM roles with OIDC or environment credentials.
5. Missing Deletion Protection
Production databases without deletion_protection can be accidentally destroyed by <a href="/devops/terraform/">terraform</a> destroy.
Practice Questions
1. How do you configure the AWS provider with IAM role assumption?
Set assume_role with role_arn and session_name in the provider "aws" block.
2. How do you retrieve the latest Amazon Linux 2 AMI dynamically?
Use the data.aws_ami data source with filters for name, owner-alias, and most_recent = true.
3. What is the purpose of default_tags in the AWS provider?
It applies common tags (Environment, ManagedBy, Project) to every resource the provider creates.
4. Challenge: Write a Terraform configuration that provisions an ALB with an EC2 auto-scaling group behind it, using a VPC module, and secured with an ACM certificate.
Mini Project: Complete AWS Web Stack
Provision a VPC with public and private subnets, an ALB in public subnets, an EC2 auto-scaling group in private subnets, an RDS PostgreSQL instance, and an S3 bucket for static assets. Configure security groups, IAM roles, and enable encryption everywhere.
Related Concepts
What's Next
Provision AWS infrastructure with Terraform for your workloads, then explore Azure with Terraform for multi-cloud deployments. Study Cloud Computing patterns for production-grade architectures.
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro