Terraform AWS Resource Examples for IAM, SQS, SNS, Lambda, S3, Route53, ALB, ElastiCache, ECS, RDS, CloudFront, Auto Scaling

Table of Contents

Terraform AWS Resource Examples

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"
      },
    ]
  })
}

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"
          }
        }
      }
    ]
  })
}

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"
}

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
}

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}/*"
      },
    ]
  })
}

Route53 Record

resource "aws_route53_record" "example_record" {
  zone_id = "ZONE_ID"
  name    = "example.com"
  type    = "A"
  ttl     = 300
  records = ["192.0.2.1"]
}

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
  }
}

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]
}

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]
  }
}

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]
}

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
  }
}

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
  }
}

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="#f5f5f5", 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="#369";
        fontcolor="#369"; style="rounded";
        root [label="main.tf\nproviders + module calls", fillcolor="#eaf2fb"];

        subgraph cluster_net {
            label="module \"network\""; labeljust="l"; color="#693";
            fontcolor="#693"; 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="#d63";
            fontcolor="#d63"; 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="#639";
            fontcolor="#639"; 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="#d36";
        fontcolor="#d36"; 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="#963", fontcolor="#963"];
        s3 -> ddb [style=dashed, label="lease", color="#d36",
                   fontcolor="#d36", fontsize=9];
    }

    // Init/plan/apply flow
    subgraph cluster_flow {
        label="Workflow"; labeljust="l"; color="#963";
        fontcolor="#963"; style="rounded";
        init  [label="terraform init", fillcolor="#fff7da"];
        plan  [label="terraform plan", fillcolor="#fff7da"];
        apply [label="terraform apply", fillcolor="#fff7da"];
        init -> plan -> apply [color="#963"];
    }

    // Composition: outputs flow forward
    root -> net_in;
    net_out -> cmp_in [label="vpc_id, subnet_ids", fontsize=9, color="#693"];
    net_out -> data_in [label="vpc_id, subnet_ids", fontsize=9, color="#693"];
    cmp_out -> data_in [style=dotted, label="security_group_id",
                        fontsize=9, color="#d63"];

    // State file gates the workflow
    init  -> s3   [label="backend config", fontsize=9, color="#369"];
    plan  -> s3   [label="read state", fontsize=9, color="#369"];
    plan  -> ddb  [label="acquire lock", fontsize=9, color="#d36"];
    apply -> ddb  [label="hold lock", fontsize=9, color="#d36"];
    apply -> s3   [label="write state", fontsize=9, color="#369"];

    // Root drives the workflow
    root -> init [style=dashed, color="#888"];
}

diagram-terraform-modules.png

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.

Related notes

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.

Author: Jason Walsh

j@wal.sh

Last Updated: 2026-04-18 21:57:45

build: 2026-04-18 22:05 | sha: 8ac55c2