Terraform AWS Resource Examples for IAM, SQS, SNS, Lambda, S3, Route53, ALB, ElastiCache, ECS, RDS, CloudFront, Auto Scaling
Table of Contents
- 1. Terraform AWS Resource Examples
- 1.1. IAM Role and Policy
- 1.2. SQS Queue and Policy
- 1.3. SNS Topic and Subscription
- 1.4. Lambda Function and CloudWatch Event Rule
- 1.5. S3 Bucket and Policy
- 1.6. Route53 Record
- 1.7. Application Load Balancer
- 1.8. ElastiCache Replication Group
- 1.9. ECS Cluster and Service
- 1.10. RDS Instance
- 1.11. CloudFront Distribution
- 1.12. Auto Scaling Target and Policy
- 2. Module composition and state
- 3. Related notes
- 4. Postscript (2026)
1. Terraform AWS Resource Examples
1.1. IAM Role and Policy
resource "aws_iam_role" "example_role" {
name = "example-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "ec2.amazonaws.com"
}
}
]
})
}
resource "aws_iam_role_policy" "example_policy" {
name = "example-policy"
role = aws_iam_role.example_role.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = [
"s3:ListBucket",
]
Effect = "Allow"
Resource = "arn:aws:s3:::example-bucket"
},
]
})
}
1.2. SQS Queue and Policy
resource "aws_sqs_queue" "example_queue" {
name = "example-queue"
delay_seconds = 90
max_message_size = 2048
message_retention_seconds = 86400
receive_wait_time_seconds = 10
}
resource "aws_sqs_queue_policy" "example_queue_policy" {
queue_url = aws_sqs_queue.example_queue.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = "*"
Action = "sqs:SendMessage"
Resource = aws_sqs_queue.example_queue.arn
Condition = {
ArnEquals = {
"aws:SourceArn" = "arn:aws:sns:us-west-2:123456789012:example-topic"
}
}
}
]
})
}
1.3. SNS Topic and Subscription
resource "aws_sns_topic" "example_topic" {
name = "example-topic"
}
resource "aws_sns_topic_subscription" "example_subscription" {
topic_arn = aws_sns_topic.example_topic.arn
protocol = "email"
endpoint = "example@example.com"
}
1.4. Lambda Function and CloudWatch Event Rule
resource "aws_lambda_function" "example_lambda" {
filename = "lambda_function.zip"
function_name = "example-lambda"
role = aws_iam_role.example_role.arn
handler = "index.handler"
runtime = "nodejs14.x"
environment {
variables = {
EXAMPLE_VAR = "example-value"
}
}
}
resource "aws_cloudwatch_event_rule" "example_event_rule" {
name = "example-event-rule"
description = "Trigger Lambda function every hour"
schedule_expression = "rate(1 hour)"
}
resource "aws_cloudwatch_event_target" "example_target" {
rule = aws_cloudwatch_event_rule.example_event_rule.name
target_id = "example-lambda"
arn = aws_lambda_function.example_lambda.arn
}
resource "aws_lambda_permission" "example_permission" {
statement_id = "AllowExecutionFromCloudWatch"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.example_lambda.function_name
principal = "events.amazonaws.com"
source_arn = aws_cloudwatch_event_rule.example_event_rule.arn
}
1.5. S3 Bucket and Policy
resource "aws_s3_bucket" "example_bucket" {
bucket = "example-bucket"
}
resource "aws_s3_bucket_policy" "example_bucket_policy" {
bucket = aws_s3_bucket.example_bucket.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "PublicReadGetObject"
Effect = "Allow"
Principal = "*"
Action = "s3:GetObject"
Resource = "${aws_s3_bucket.example_bucket.arn}/*"
},
]
})
}
1.6. Route53 Record
resource "aws_route53_record" "example_record" {
zone_id = "ZONE_ID"
name = "example.com"
type = "A"
ttl = 300
records = ["192.0.2.1"]
}
1.7. Application Load Balancer
resource "aws_lb" "example_alb" {
name = "example-alb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.example_sg.id]
subnets = ["subnet-12345678", "subnet-87654321"]
enable_deletion_protection = false
}
resource "aws_lb_target_group" "example_tg" {
name = "example-tg"
port = 80
protocol = "HTTP"
vpc_id = "vpc-12345678"
}
resource "aws_lb_listener" "example_listener" {
load_balancer_arn = aws_lb.example_alb.arn
port = "80"
protocol = "HTTP"
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.example_tg.arn
}
}
1.8. ElastiCache Replication Group
resource "aws_elasticache_subnet_group" "example_subnet_group" {
name = "example-cache-subnet"
subnet_ids = ["subnet-12345678", "subnet-87654321"]
}
resource "aws_elasticache_replication_group" "example_cache" {
replication_group_id = "example-cache"
replication_group_description = "Example Redis cluster"
node_type = "cache.t3.micro"
number_cache_clusters = 2
port = 6379
subnet_group_name = aws_elasticache_subnet_group.example_subnet_group.name
security_group_ids = [aws_security_group.example_sg.id]
}
1.9. ECS Cluster and Service
resource "aws_ecs_cluster" "example_cluster" {
name = "example-cluster"
}
resource "aws_ecs_task_definition" "example_task" {
family = "example-task"
network_mode = "awsvpc"
requires_compatibilities = ["FARGATE"]
cpu = "256"
memory = "512"
container_definitions = jsonencode([
{
name = "example-container"
image = "nginx:latest"
portMappings = [
{
containerPort = 80
hostPort = 80
}
]
}
])
}
resource "aws_ecs_service" "example_service" {
name = "example-service"
cluster = aws_ecs_cluster.example_cluster.id
task_definition = aws_ecs_task_definition.example_task.arn
launch_type = "FARGATE"
desired_count = 2
network_configuration {
subnets = ["subnet-12345678", "subnet-87654321"]
security_groups = [aws_security_group.example_sg.id]
}
}
1.10. RDS Instance
resource "aws_db_subnet_group" "example_subnet_group" {
name = "example-db-subnet"
subnet_ids = ["subnet-12345678", "subnet-87654321"]
}
resource "aws_db_instance" "example_db" {
identifier = "example-db"
engine = "mysql"
engine_version = "5.7"
instance_class = "db.t3.micro"
allocated_storage = 20
storage_type = "gp2"
db_name = "exampledb"
username = "admin"
password = "PASSWORD"
parameter_group_name = "default.mysql5.7"
skip_final_snapshot = true
db_subnet_group_name = aws_db_subnet_group.example_subnet_group.name
vpc_security_group_ids = [aws_security_group.example_sg.id]
}
1.11. CloudFront Distribution
resource "aws_cloudfront_distribution" "example_distribution" {
enabled = true
default_root_object = "index.html"
origin {
domain_name = aws_s3_bucket.example_bucket.bucket_regional_domain_name
origin_id = "S3-${aws_s3_bucket.example_bucket.id}"
}
default_cache_behavior {
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "S3-${aws_s3_bucket.example_bucket.id}"
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
viewer_protocol_policy = "redirect-to-https"
min_ttl = 0
default_ttl = 3600
max_ttl = 86400
}
restrictions {
geo_restriction {
restriction_type = "none"
}
}
viewer_certificate {
cloudfront_default_certificate = true
}
}
1.12. Auto Scaling Target and Policy
resource "aws_appautoscaling_target" "example_target" {
max_capacity = 4
min_capacity = 1
resource_id = "service/${aws_ecs_cluster.example_cluster.name}/${aws_ecs_service.example_service.name}"
scalable_dimension = "ecs:service:DesiredCount"
service_namespace = "ecs"
}
resource "aws_appautoscaling_policy" "example_policy" {
name = "example-autoscaling-policy"
policy_type = "TargetTrackingScaling"
resource_id = aws_appautoscaling_target.example_target.resource_id
scalable_dimension = aws_appautoscaling_target.example_target.scalable_dimension
service_namespace = aws_appautoscaling_target.example_target.service_namespace
target_tracking_scaling_policy_configuration {
predefined_metric_specification {
predefined_metric_type = "ECSServiceAverageCPUUtilization"
}
target_value = 70.0
}
}
2. Module composition and state
The resource snippets above belong to a single root module. In real
projects they get split into reusable modules (network, compute,
data) wired together by the root, with a remote backend gating every
plan and apply via a state file and a lock.
// Terraform module composition + remote state — root composes // network/compute/data; S3 + DynamoDB lock gates init/plan/apply. digraph terraform_modules { rankdir=TB; graph [bgcolor="white", fontname="Helvetica", fontsize=11, pad="0.3", nodesep="0.3", ranksep="0.4"]; node [shape=box, style="rounded,filled", fontname="Helvetica", fontsize=10, fillcolor="#dbeafe", color="#888"]; edge [color="#aaa"]; // Tailwind palette: #d36 red, #d63 orange, #693 green, #369 blue, #639 purple, #963 brown // Root module composes reusable submodules subgraph cluster_root { label="Root module (env: prod)"; labeljust="l"; color="#1d4ed8"; fontcolor="#1d4ed8"; style="rounded"; root [label="main.tf\nproviders + module calls", fillcolor="#eaf2fb"]; subgraph cluster_net { label="module \"network\""; labeljust="l"; color="#15803d"; fontcolor="#15803d"; style="rounded"; net_in [label="inputs:\ncidr, az_count"]; net_out [label="outputs:\nvpc_id, subnet_ids", fillcolor="#eaf5ea"]; } subgraph cluster_cmp { label="module \"compute\""; labeljust="l"; color="#b45309"; fontcolor="#b45309"; style="rounded"; cmp_in [label="inputs:\nvpc_id, subnet_ids,\nimage, instance_type"]; cmp_out [label="outputs:\nasg_name, alb_dns", fillcolor="#fbeee5"]; } subgraph cluster_data { label="module \"data\""; labeljust="l"; color="#6b21a8"; fontcolor="#6b21a8"; style="rounded"; data_in [label="inputs:\nvpc_id, subnet_ids,\nengine, size"]; data_out [label="outputs:\ndb_endpoint, secret_arn", fillcolor="#f0eaf5"]; } } // Side cluster: remote state + locking subgraph cluster_state { label="Remote backend"; labeljust="l"; color="#b91c1c"; fontcolor="#b91c1c"; style="rounded"; s3 [label="S3 bucket\nterraform.tfstate", fillcolor="#fbe6ec"]; ddb [label="DynamoDB table\nstate lock", fillcolor="#fbe6ec"]; tfc [label="(or) Terraform Cloud /\nOpenTofu remote state", fillcolor="#fff7da", color="#a16207", fontcolor="#a16207"]; s3 -> ddb [style=dashed, label="lease", color="#b91c1c", fontcolor="#b91c1c", fontsize=9]; } // Init/plan/apply flow subgraph cluster_flow { label="Workflow"; labeljust="l"; color="#a16207"; fontcolor="#a16207"; style="rounded"; init [label="terraform init", fillcolor="#fff7da"]; plan [label="terraform plan", fillcolor="#fff7da"]; apply [label="terraform apply", fillcolor="#fff7da"]; init -> plan -> apply [color="#a16207"]; } // Composition: outputs flow forward root -> net_in; net_out -> cmp_in [label="vpc_id, subnet_ids", fontsize=9, color="#15803d"]; net_out -> data_in [label="vpc_id, subnet_ids", fontsize=9, color="#15803d"]; cmp_out -> data_in [style=dotted, label="security_group_id", fontsize=9, color="#b45309"]; // State file gates the workflow init -> s3 [label="backend config", fontsize=9, color="#1d4ed8"]; plan -> s3 [label="read state", fontsize=9, color="#1d4ed8"]; plan -> ddb [label="acquire lock", fontsize=9, color="#b91c1c"]; apply -> ddb [label="hold lock", fontsize=9, color="#b91c1c"]; apply -> s3 [label="write state", fontsize=9, color="#1d4ed8"]; // Root drives the workflow root -> init [style=dashed, color="#888"]; }
The module boundary matters because each submodule's outputs become
the next module's inputs. The state file is the gating artifact: a
plan without the lease is a guess, and an apply without the lock
risks two operators mutating the same resources in parallel.
3. Related notes
- Terraform — AWS Lambda configuration system — graph-style view of a single root module's resource dependencies, complementary to the module-composition view above.
- Amazon DynamoDB: NoSQL Database Design — the locking table in the diagram is a DynamoDB table; access-pattern notes there explain why a single-key conditional write is enough for state locking.
- CI/CD Pipelines — the
init → plan → applyflow is what most CI/CD pipelines wrap; GitOps and pull-request-driven plans live in that layer. - Software Design and Architecture Patterns — module composition, outputs as contracts, and ADR-style decision records are architecture concerns the snippets here only hint at.
4. Postscript (2026)
The resource snippets above still type-check, but the ground around
them has moved. In August 2023 HashiCorp relicensed Terraform from MPL
2.0 to the Business Source License, prompting a community fork that
became OpenTofu under the Linux Foundation and reached GA 1.6 in
January 2024 with a drop-in CLI and state-file compatibility
(OpenTofu 1.6 GA announcement). Pulumi pushed in the opposite
direction, leaning into TypeScript/Python/Go as first-class IaC
languages and shipping Pulumi Copilot for natural-language stack
operations. For shops staying on Terraform/OpenTofu, the conventions
that matured are Terragrunt for DRY backend + tfvars wiring, internal
module registries (Spacelift, env0, Terraform Cloud private registry)
to replace ad-hoc Git-tagged modules, and hardened remote backends
with KMS-encrypted state plus DynamoDB or native HCP locking. The
2023 terraform_remote_state data-source advisory
(HashiCorp HCSEC advisories) made "remote backend + least-privilege
IAM" the table-stakes default rather than an optional hardening step.