Fatskills
Practice. Master. Repeat.
Study Guide: TECH **Terraform Resource Attributes & Dependencies: Zero-Fluff, Hands-On Guide**
Source: https://www.fatskills.com/cloud-application-developer/chapter/tech-terraform-resource-attributes-dependencies-zero-fluff-hands-on-guide

TECH **Terraform Resource Attributes & Dependencies: Zero-Fluff, Hands-On Guide**

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

⏱️ ~10 min read

Terraform Resource Attributes & Dependencies: Zero-Fluff, Hands-On Guide

(For Cloud Engineers, DevOps, and Certification Takers)


1. What This Is & Why It Matters

You’re deploying a VPC with a private subnet, an RDS database, and an EC2 instance that needs to connect to that database. You write the Terraform config, run terraform apply, and… the EC2 instance launches before the RDS endpoint is ready. The app crashes, logs fill with Connection refused, and your pager goes off at 2 AM.

This is why resource attributes and dependencies matter.


  • Resource attributes (id, arn, tags, etc.) are the outputs of Terraform resources—what you use to reference them elsewhere in your config (e.g., passing an RDS endpoint to an EC2 user-data script).
  • Dependencies (depends_on) tell Terraform what order to create resources in, even when there’s no explicit reference (e.g., "Wait for this IAM role to exist before launching the EC2 instance").

In production, ignoring these means:
Race conditions (resources created in the wrong order).
Broken references (e.g., trying to use an arn that doesn’t exist yet).
Tagging chaos (cost tracking, security compliance, and automation all rely on tags).
Failed audits (missing Name tags? That’s a red flag in most enterprise environments).

Real-world scenario:
You inherit a Terraform repo where someone hardcoded an S3 bucket name (my-bucket-123) instead of using the arn attribute. Later, the bucket name changes, and every reference breaks. Now you’re debugging a 500-line config instead of just updating one line.


2. Core Concepts & Components


1. Resource Attributes

  • Definition: Metadata exposed by a Terraform resource after creation (e.g., aws_instance.web.id, aws_s3_bucket.data.arn).
  • Production insight: Always reference attributes dynamically (never hardcode IDs/ARNs). If you hardcode, your config breaks when resources are recreated (e.g., during a terraform destroy + apply).

2. id Attribute

  • Definition: A unique identifier for a resource (e.g., i-0abc1234567890def for an EC2 instance).
  • Production insight: Use id for low-level references (e.g., attaching an EBS volume to an EC2 instance). Avoid hardcoding—use aws_instance.web.id instead.

3. arn Attribute

  • Definition: Amazon Resource Name—a globally unique identifier for AWS resources (e.g., arn:aws:s3:::my-bucket).
  • Production insight: Required for IAM policies, cross-account access, and event triggers (e.g., Lambda permissions, SNS subscriptions). Never guess an ARN—always use the attribute.

4. tags Attribute

  • Definition: Key-value pairs attached to resources (e.g., Environment = "prod", Owner = "team-infra").
  • Production insight:
  • Cost allocation: AWS Cost Explorer filters by tags.
  • Security: IAM policies can restrict access by tag (e.g., Condition: { "StringEquals": { "aws:ResourceTag/Environment": "prod" } }).
  • Automation: Tools like AWS Backup, Config, and Lambda use tags to target resources.

5. Implicit Dependencies

  • Definition: Terraform automatically infers dependencies when one resource references another’s attribute (e.g., vpc_id = aws_vpc.main.id).
  • Production insight: Implicit dependencies are safer than depends_on—they ensure resources are created in the correct order without manual intervention.

6. Explicit Dependencies (depends_on)

  • Definition: Forces Terraform to create resources in a specific order, even if there’s no attribute reference.
  • Production insight: Use sparingly! Overusing depends_on makes your config fragile and hard to debug. Only use it when:
  • A resource must exist before another (e.g., IAM role before EC2 instance).
  • There’s no attribute reference (e.g., waiting for a CloudFormation stack to complete).

7. Outputs (for Sharing Attributes)

  • Definition: Exposes resource attributes to other Terraform modules or the CLI (e.g., output "db_endpoint" { value = aws_db_instance.main.endpoint }).
  • Production insight: Outputs are critical for modular Terraform. Without them, you can’t pass values between modules (e.g., VPC ID to a database module).

8. Data Sources (for Existing Resources)

  • Definition: Fetches attributes of existing resources (e.g., data "aws_vpc" "default" { default = true }).
  • Production insight: Use data sources to avoid hardcoding (e.g., referencing a VPC created outside Terraform). Warning: Overuse can make your config less portable.


3. Step-by-Step Hands-On: Deploy a Secure RDS + EC2 Setup


Prerequisites

  • AWS account with admin IAM permissions (or at least EC2, RDS, VPC, IAM access).
  • Terraform installed (terraform --version should work).
  • AWS CLI configured (aws sts get-caller-identity should return your user).

Goal

Deploy: 1. A VPC with a private subnet.
2. An RDS PostgreSQL database (private).
3. An EC2 instance (in the same VPC) that connects to the RDS.
4. Tags for cost tracking and security.
5. Explicit dependencies to ensure the RDS is ready before the EC2 launches.


Step 1: Initialize Terraform

mkdir tf-attributes-dependencies && cd tf-attributes-dependencies
touch main.tf

Step 2: Write the Config (main.tf)

terraform {
  required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
} } } provider "aws" { region = "us-east-1" } # 1. VPC + Subnet resource "aws_vpc" "main" { cidr_block = "10.0.0.0/16" tags = {
Name = "main-vpc"
Environment = "dev"
Owner = "team-infra" } } resource "aws_subnet" "private" { vpc_id = aws_vpc.main.id # Implicit dependency cidr_block = "10.0.1.0/24" availability_zone = "us-east-1a" tags = {
Name = "private-subnet" } } # 2. Security Group for RDS resource "aws_security_group" "rds" { name = "rds-sg" description = "Allow PostgreSQL traffic from EC2" vpc_id = aws_vpc.main.id ingress {
from_port = 5432
to_port = 5432
protocol = "tcp"
cidr_blocks = [aws_subnet.private.cidr_block] # Reference subnet CIDR } tags = {
Name = "rds-sg" } } # 3. RDS PostgreSQL (private) resource "aws_db_instance" "postgres" { identifier = "my-postgres-db" engine = "postgres" engine_version = "15.4" instance_class = "db.t3.micro" allocated_storage = 20 db_name = "mydb" username = "admin" password = "SuperSecret123!" # ⚠️ Use AWS Secrets Manager in prod! db_subnet_group_name = aws_db_subnet_group.main.name vpc_security_group_ids = [aws_security_group.rds.id] skip_final_snapshot = true # ⚠️ Disable in prod! tags = {
Name = "postgres-db"
Environment = "dev" } } resource "aws_db_subnet_group" "main" { name = "main-subnet-group" subnet_ids = [aws_subnet.private.id] tags = {
Name = "main-subnet-group" } } # 4. IAM Role for EC2 (to access RDS) resource "aws_iam_role" "ec2_rds_access" { name = "ec2-rds-access-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_attachment" "rds_full_access" { role = aws_iam_role.ec2_rds_access.name policy_arn = "arn:aws:iam::aws:policy/AmazonRDSFullAccess" } # 5. EC2 Instance (depends on RDS + IAM role) resource "aws_instance" "app" { ami = "ami-0c55b159cbfafe1f0" # Amazon Linux 2 (us-east-1) instance_type = "t3.micro" subnet_id = aws_subnet.private.id iam_instance_profile = aws_iam_instance_profile.ec2_rds_access.name # Explicit dependency: Wait for RDS to be ready depends_on = [aws_db_instance.postgres, aws_iam_role.ec2_rds_access] user_data = <<-EOF
#!/bin/bash
yum install -y postgresql
echo "RDS endpoint: ${aws_db_instance.postgres.endpoint}" >> /home/ec2-user/rds_endpoint.txt
EOF tags = {
Name = "app-server"
Environment = "dev"
Role = "database-client" } } resource "aws_iam_instance_profile" "ec2_rds_access" { name = "ec2-rds-access-profile" role = aws_iam_role.ec2_rds_access.name } # 6. Output the RDS endpoint for debugging output "rds_endpoint" { value = aws_db_instance.postgres.endpoint }


Step 3: Deploy the Infrastructure

terraform init
terraform plan  # Verify no errors
terraform apply -auto-approve

Step 4: Verify Success

  1. Check the RDS endpoint:
    bash
    terraform output rds_endpoint

    Output should look like: my-postgres-db.abc123xyz.us-east-1.rds.amazonaws.com:5432.

  2. SSH into the EC2 instance and check the RDS endpoint:
    bash
    ssh -i your-key.pem ec2-user@<EC2_PUBLIC_IP>
    cat /home/ec2-user/rds_endpoint.txt

    (Replace <EC2_PUBLIC_IP> with the instance’s public IP from the AWS Console.)

  3. Check tags in AWS Console:

  4. Go to EC2 > Instances > Tags.
  5. Go to RDS > Databases > Tags.

Step 5: Clean Up

terraform destroy -auto-approve


4. ? Production-Ready Best Practices


Security

  • Never hardcode secrets (e.g., RDS passwords). Use: hcl password = var.db_password # Pass via Terraform variables or AWS Secrets Manager
  • Least privilege IAM roles: Instead of AmazonRDSFullAccess, scope down to: hcl policy_arn = "arn:aws:iam::aws:policy/AmazonRDSReadOnlyAccess"
  • Tag-based IAM policies: Restrict access by tag (e.g., only allow Environment=prod resources to be modified).

Cost Optimization

  • Tag everything for cost allocation: hcl tags = {
    CostCenter = "marketing"
    Project = "website-redesign" }
  • Use lifecycle to prevent accidental deletions:
    hcl resource "aws_db_instance" "postgres" {
    lifecycle {
    prevent_destroy = true # ⚠️ Terraform will error if you try to destroy this
    } }

Reliability & Maintainability

  • Avoid depends_on unless absolutely necessary. Prefer implicit dependencies (attribute references).
  • Use outputs for cross-module references:
    ```hcl # In vpc_module/outputs.tf output "vpc_id" {
    value = aws_vpc.main.id }

# In main.tf module "vpc" {
source = "./vpc_module" }

resource "aws_subnet" "private" {
vpc_id = module.vpc.vpc_id # Reference the output } `` - Standardize tagging: Enforce a tagging schema (e.g.,Environment,Owner,Project`).

Observability

  • Monitor RDS connections: CloudWatch metric DatabaseConnections should match your app’s expected load.
  • Log EC2 user-data scripts: Debug failures by checking /var/log/cloud-init-output.log.
  • Alert on tag compliance: Use AWS Config rules to flag untagged resources.


5. ⚠️ Common Mistakes & Traps

Mistake Symptom Fix/Prevention
Hardcoding ARNs/IDs Terraform fails after destroy + apply Always use attributes (e.g., aws_s3_bucket.data.arn).
Overusing depends_on Slow deployments, fragile config Prefer implicit dependencies (attribute references). Only use depends_on for non-attribute dependencies (e.g., IAM roles).
Missing Name tag AWS Console becomes unmanageable Enforce a Name tag for all resources.
Not outputting critical attributes Can’t reference values in other modules Output everything you’ll need later (e.g., vpc_id, rds_endpoint).
Ignoring lifecycle rules Accidental deletions of prod databases Add prevent_destroy = true to critical resources.


6. ? Exam/Certification Focus


Typical Question Patterns

  1. "What’s the correct way to reference an S3 bucket ARN in another resource?"
  2. arn:aws:s3:::my-bucket (hardcoded)
  3. aws_s3_bucket.data.arn (dynamic)

  4. "When should you use depends_on?"

  5. ✅ When a resource must exist before another, but there’s no attribute reference (e.g., IAM role before EC2).
  6. ❌ When you can use an implicit dependency (e.g., vpc_id = aws_vpc.main.id).

  7. "How do you ensure an EC2 instance is created after an RDS database?"

  8. ✅ Use depends_on = [aws_db_instance.postgres] or reference the RDS endpoint in user-data.

Key Trap Distinctions

  • Implicit vs. Explicit Dependencies:
  • Implicit: Terraform infers order from attribute references (e.g., vpc_id = aws_vpc.main.id).
  • Explicit: You manually specify order with depends_on.
  • Tags vs. Attributes:
  • Tags: User-defined metadata (e.g., Environment = "prod").
  • Attributes: System-generated (e.g., id, arn).

Scenario-Based Question

"You need to deploy a Lambda function that reads from an S3 bucket. The bucket must exist before the Lambda is created. How do you ensure this?"

Answer:


resource "aws_lambda_function" "example" {
  # ... other config ...
depends_on = [aws_s3_bucket.data] # Explicit dependency }

Why?
- The Lambda needs the bucket to exist, but there’s no attribute reference in the Lambda config.
- depends_on forces Terraform to create the bucket first.


7. ? Hands-On Challenge

Challenge:
Deploy an S3 bucket and an IAM policy that grants read access to the bucket. The IAM policy must use the bucket’s ARN dynamically (no hardcoding). After deployment, verify the policy works by uploading a file to the bucket and checking if an IAM user can read it.

Solution:


resource "aws_s3_bucket" "data" {
  bucket = "my-unique-bucket-name-123"  # Must be globally unique!
}

resource "aws_iam_policy" "bucket_read" {
  name        = "s3-read-policy"
  description = "Allows read access to the S3 bucket"

  policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = ["s3:GetObject"]
Effect = "Allow"
Resource = aws_s3_bucket.data.arn # Dynamic ARN reference
}
] }) } # Verify by attaching the policy to a user and testing access

Why it works:
- The IAM policy uses aws_s3_bucket.data.arn to dynamically reference the bucket ARN.
- If the bucket name changes, the policy automatically updates.


8. ? Rapid-Reference Crib Sheet

Concept Command/Code Notes
Reference an attribute aws_instance.web.id Never hardcode IDs/ARNs.
Output an attribute output "vpc_id" { value = aws_vpc.main.id } Useful for cross-module references.
Implicit dependency vpc_id = aws_vpc.main.id Terraform infers order automatically.
Explicit dependency depends_on = [aws_db_instance.postgres] Use sparingly!
Tag a resource tags = { Name = "my-resource", Environment = "prod" } Always include Name and Environment.
Data source (existing) data "aws_vpc" "default" { default = true } Avoid overuse—makes config less portable.
⚠️ Default RDS retention skip_final_snapshot = false (default) Always set final_snapshot_identifier in prod!
⚠️ S3 bucket naming Must be globally unique (e.g., my-bucket-123xyz). Use a naming convention (e.g., <org>-<project>-<env>).
⚠️ IAM policy ARNs arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess Prefer AWS-managed policies over custom ones.


9. ? Where to Go Next

  1. Terraform Docs: Resource Attributes
    2


ADVERTISEMENT