Fatskills
Practice. Master. Repeat.
Study Guide: TECH **Terraform Resources & Data Sources: Zero-Fluff, Hands-On Study Guide**
Source: https://www.fatskills.com/cloud-application-developer/chapter/tech-terraform-resources-data-sources-zero-fluff-hands-on-study-guide

TECH **Terraform Resources & Data Sources: Zero-Fluff, Hands-On Study Guide**

By Fatskills Exam Guides Team — the exam nerds behind 28,500+ quizzes and 2.1M practice questions across 500+ global exams.

⏱️ ~8 min read

Terraform Resources & Data Sources: Zero-Fluff, Hands-On Study Guide

(For Cloud Engineers, DevOps, and Certification Prep)


1. What This Is & Why It Matters

Terraform resources are the building blocks of your infrastructure—think EC2 instances, S3 buckets, or IAM roles. Data sources, on the other hand, let you read existing infrastructure (e.g., an AMI ID, a VPC’s CIDR block, or a user’s ARN) without creating it.

Why This Matters in Production

  • Resources = "I need to create a new RDS database."
  • Data sources = "I need to find the latest Amazon Linux AMI to use for my EC2 launch template."

Real-world scenario:
You’re deploying a new microservice. You need: 1. A resource to create an ECS cluster.
2. A data source to fetch the existing VPC’s subnet IDs (so you don’t hardcode them).
3. Another data source to grab the latest ECS-optimized AMI.

What breaks if you ignore this?
- Hardcoding values (e.g., AMI IDs) → Your Terraform breaks when AWS updates the AMI.
- Not using data sources → You duplicate infrastructure (e.g., creating a new VPC when one already exists).
- Poor referencing → Circular dependencies (e.g., trying to attach a security group to an instance before the security group exists).


2. Core Concepts & Components


? resource Block

  • Definition: Declares a piece of infrastructure to create or manage.
  • Example:
    hcl resource "aws_instance" "web" {
    ami = "ami-0c55b159cbfafe1f0"
    instance_type = "t3.micro" }
  • Production insight:
  • Always use variables for ami and instance_type (don’t hardcode).
  • Set lifecycle rules (e.g., prevent_destroy = true for critical DBs).

? data Block

  • Definition: Fetches existing infrastructure details (read-only).
  • Example:
    hcl data "aws_vpc" "default" {
    default = true }
  • Production insight:
  • Use data sources to avoid hardcoding (e.g., data "aws_subnets" "private" instead of listing subnet IDs).
  • Data sources are evaluated at runtime, not plan time (unlike locals).

? depends_on

  • Definition: Explicitly sets resource dependencies (Terraform usually infers this).
  • Example:
    hcl resource "aws_db_instance" "app_db" {
    depends_on = [aws_security_group.db_sg] }
  • Production insight:
  • Use sparingly—Terraform’s implicit dependencies (via references) are usually enough.
  • Overuse can slow down terraform plan.

? References (resource_type.name.attribute)

  • Definition: How you link resources (e.g., attach a security group to an EC2 instance).
  • Example:
    hcl resource "aws_instance" "web" {
    vpc_security_group_ids = [aws_security_group.web_sg.id] }
  • Production insight:
  • References create implicit dependencies (Terraform knows web_sg must exist before web).
  • Avoid circular dependencies (e.g., A depends on B, and B depends on A).

? count and for_each

  • Definition: Create multiple instances of a resource.
  • Example (count):
    hcl resource "aws_instance" "web" {
    count = 3
    ami = "ami-0c55b159cbfafe1f0" }
  • Example (for_each):
    hcl resource "aws_iam_user" "developers" {
    for_each = toset(["alice", "bob", "charlie"])
    name = each.key }
  • Production insight:
  • for_each is safer than count (no index shifts if you delete an item).
  • Use count for simple lists, for_each for maps/sets.

? lifecycle Rules

  • Definition: Controls how Terraform manages resource changes.
  • Example:
    hcl resource "aws_instance" "web" {
    lifecycle {
    create_before_destroy = true # Replace instance before destroying old one
    prevent_destroy = true # Block accidental deletion
    } }
  • Production insight:
  • create_before_destroy is critical for zero-downtime deployments (e.g., ASG instances).
  • prevent_destroy is a safeguard for production databases.

? output Block

  • Definition: Exposes values (e.g., an EC2 instance’s public IP) for use in other Terraform configs or CLI.
  • Example:
    hcl output "instance_ip" {
    value = aws_instance.web.public_ip }
  • Production insight:
  • Use outputs to share data between Terraform modules.
  • Mark sensitive outputs with sensitive = true (e.g., DB passwords).

? terraform import

  • Definition: Brings existing infrastructure under Terraform management.
  • Example:
    bash terraform import aws_instance.web i-1234567890abcdef0
  • Production insight:
  • Always back up the state file before importing.
  • After import, manually add the resource to your .tf file to match the imported state.


3. Step-by-Step Hands-On: Deploy a Private ECS Cluster with Data Sources


Prerequisites

  • AWS account with admin IAM permissions.
  • AWS CLI configured (aws configure).
  • Terraform installed (terraform --version).

Task

Deploy an ECS cluster in an existing VPC (using data sources to fetch subnets and security groups).

Step 1: Initialize Terraform

mkdir ecs-cluster && cd ecs-cluster
terraform init

Step 2: Fetch Existing VPC and Subnets

Create main.tf:


# Fetch the default VPC
data "aws_vpc" "default" {
  default = true
}

# Fetch all subnets in the VPC
data "aws_subnets" "all" {
  filter {
name = "vpc-id"
values = [data.aws_vpc.default.id] } } # Fetch the default security group data "aws_security_group" "default" { vpc_id = data.aws_vpc.default.id name = "default" }

Step 3: Create an ECS Cluster

Add to main.tf:


resource "aws_ecs_cluster" "app" {
  name = "app-cluster"
}

resource "aws_ecs_task_definition" "app" {
  family                   = "app-task"
  network_mode             = "awsvpc"
  requires_compatibilities = ["FARGATE"]
  cpu                      = 256
  memory                   = 512
  execution_role_arn       = aws_iam_role.ecs_task_execution.arn

  container_definitions = jsonencode([{
name = "app"
image = "nginx:latest"
portMappings = [{
containerPort = 80
hostPort = 80
}] }]) } # IAM role for ECS task execution resource "aws_iam_role" "ecs_task_execution" { name = "ecs-task-execution-role" assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "ecs-tasks.amazonaws.com"
}
}] }) } resource "aws_iam_role_policy_attachment" "ecs_task_execution" { role = aws_iam_role.ecs_task_execution.name policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" }

Step 4: Deploy a Fargate Service

Add to main.tf:


resource "aws_ecs_service" "app" {
  name            = "app-service"
  cluster         = aws_ecs_cluster.app.id
  task_definition = aws_ecs_task_definition.app.arn
  desired_count   = 1
  launch_type     = "FARGATE"

  network_configuration {
subnets = data.aws_subnets.all.ids
security_groups = [data.aws_security_group.default.id]
assign_public_ip = true } }

Step 5: Apply the Configuration

terraform plan  # Verify no hardcoded values
terraform apply

Step 6: Verify

  1. Go to AWS ECS Console → Check the app-cluster.
  2. Go to ECS Services → Verify app-service is running.
  3. Check the Tasks tab → Click the task → Note the public IP.
  4. Curl the IP:
    bash
    curl <TASK_PUBLIC_IP>

    (You should see the NGINX welcome page.)

4. ? Production-Ready Best Practices


Security

  • Least privilege: Restrict IAM roles (e.g., ECS task roles should only have permissions they need).
  • Secrets: Never hardcode secrets in .tf files. Use: hcl data "aws_secretsmanager_secret_version" "db_password" {
    secret_id = "prod/db/password" }
  • Network isolation: Use private subnets for databases, public for load balancers.

Cost Optimization

  • Right-size resources: Use t3.micro for dev, m5.large for prod.
  • Spot instances: For fault-tolerant workloads (e.g., ECS tasks).
  • S3 lifecycle policies: Move old logs to S3 Glacier after 30 days.

Reliability & Maintainability

  • Tagging: Always tag resources (e.g., Environment = "prod", Owner = "team-x").
  • Naming conventions: Use project-environment-resource (e.g., app-prod-alb).
  • Idempotency: Terraform should produce the same result every apply.

Observability

  • CloudWatch alarms: Monitor CPU/memory for ECS tasks.
  • Terraform state: Store in S3 with DynamoDB locking (prevents concurrent edits).
  • Logging: Enable ECS task logging to CloudWatch.


5. ⚠️ Common Mistakes & Traps

Mistake Symptom Fix/Prevention
Hardcoding values (e.g., AMI IDs) Terraform fails when AWS updates the AMI. Use data "aws_ami" "latest" to fetch the latest AMI.
Circular dependencies terraform plan hangs or errors. Restructure references (e.g., move security group to a separate module).
Not using lifecycle rules Accidental deletion of production DBs. Add prevent_destroy = true to critical resources.
Overusing depends_on Slow terraform plan times. Let Terraform infer dependencies via references (e.g., aws_instance.web.sg_id).
Forgetting output for shared data Other Terraform configs can’t use the data. Always output values needed by other modules (e.g., VPC ID, subnet IDs).


6. ? Exam/Certification Focus


Typical Question Patterns

  1. "Which block fetches an existing VPC?"
  2. resource "aws_vpc" "main" {...}
  3. data "aws_vpc" "main" {...}

  4. "How do you reference a security group in an EC2 instance?"

  5. vpc_security_group_ids = [aws_security_group.web.id]
  6. security_groups = ["web_sg"] (wrong attribute)

  7. "What happens if you delete a resource from .tf but not from state?"

  8. Terraform will recreate it on the next apply.

Key Trap Distinctions

  • resource vs data:
  • resource = create/modify.
  • data = read-only.
  • count vs for_each:
  • count = index-based (risky if items are deleted).
  • for_each = key-based (safer).

Scenario-Based Question

"You need to deploy an EC2 instance in an existing VPC. How do you avoid hardcoding subnet IDs?"
- ✅ Use data "aws_subnets" "private" to fetch subnets dynamically.
- ❌ Hardcode subnet_id = "subnet-123456" (breaks if subnets change).


7. ? Hands-On Challenge


Challenge

Deploy an S3 bucket with a lifecycle rule that moves objects to S3 Glacier after 30 days. Use a data source to fetch the current AWS account ID (to avoid hardcoding it in the bucket name).

Solution

data "aws_caller_identity" "current" {}

resource "aws_s3_bucket" "logs" {
  bucket = "app-logs-${data.aws_caller_identity.current.account_id}"
  acl    = "private"

  lifecycle_rule {
id = "move-to-glacier"
enabled = true
transition {
days = 30
storage_class = "GLACIER"
} } }

Why it works:
- data.aws_caller_identity.current fetches the AWS account ID dynamically.
- The bucket name is globally unique (required for S3).
- The lifecycle rule automates cost savings.


8. ? Rapid-Reference Crib Sheet

Command/Concept Example Notes
resource block resource "aws_instance" "web" {...} Creates infrastructure.
data block data "aws_vpc" "default" {...} Fetches existing infrastructure.
Reference a resource aws_instance.web.public_ip Use in other resources or outputs.
depends_on depends_on = [aws_security_group.web_sg] Explicit dependency (use sparingly).
count count = 3 Creates multiple instances (index-based).
for_each for_each = toset(["alice", "bob"]) Creates multiple instances (key-based).
lifecycle lifecycle { create_before_destroy = true } Controls resource replacement.
terraform import terraform import aws_instance.web i-1234567890abcdef0 Brings existing infra under Terraform management.
output output "instance_ip" { value = aws_instance.web.public_ip } Exposes values for use elsewhere.
⚠️ Default S3 bucket ACL acl = "private" Default is private (not public!).
⚠️ Default RDS backup retention backup_retention_period = 1 Change this! Default is 1 day (too short for prod).


9. ? Where to Go Next

  1. Terraform Docs: Resources
  2. Terraform Docs: Data Sources
  3. AWS Provider Docs
  4. Book: Terraform: Up & Running (Yevgeniy Brikman) – Chapter 4 (Resources) and Chapter 5 (Modules).

Final Tip:
Always run terraform plan before apply. If the plan shows unexpected changes, stop and investigate—it’s cheaper to debug now than fix a broken prod environment later. ?



ADVERTISEMENT