Terraform Data Sources Explained: Read External Information
Learn Terraform data sources to read existing AWS resources, look up AMIs, query remote state, and reference external information in your configurations.
DevOps
Learn when to use Terraform depends_on for explicit resource dependencies. Understand implicit vs explicit dependencies, common use cases
Terraform automatically determines resource ordering from references in your code. You rarely need depends_on — but when you do, it's critical. This guide explains when implicit dependencies work, when they don't, and when depends_on is the right tool.
Terraform builds a dependency graph from resource references:
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
}
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id # ← Reference creates implicit dependency
cidr_block = "10.0.1.0/24"
}
resource "aws_instance" "web" {
subnet_id = aws_subnet.public.id # ← Depends on subnet
ami = "ami-abc123"
instance_type = "t3.micro"
}Terraform knows: VPC → Subnet → Instance. No depends_on needed.
terraform graph:
aws_vpc.main
└── aws_subnet.public
└── aws_instance.webThe most common case. IAM policies take seconds to propagate across AWS:
resource "aws_iam_role" "lambda" {
name = "lambda-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = { Service = "lambda.amazonaws.com" }
Action = "sts:AssumeRole"
}]
})
}
resource "aws_iam_role_policy_attachment" "lambda_s3" {
role = aws_iam_role.lambda.name
policy_arn = "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess"
}
resource "aws_lambda_function" "main" {
function_name = "processor"
role = aws_iam_role.lambda.arn
runtime = "python3.12"
handler = "main.handler"
filename = "lambda.zip"
# Lambda creation might fail if IAM policy hasn't propagated
depends_on = [aws_iam_role_policy_attachment.lambda_s3]
}Without depends_on, Terraform sees the role ARN reference (implicit dep on the role) but not on the policy attachment. The Lambda might be created before the policy attaches, causing permission errors on first invocation.
resource "aws_s3_bucket" "logs" {
bucket = "my-access-logs"
}
resource "aws_s3_bucket_policy" "logs" {
bucket = aws_s3_bucket.logs.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = { Service = "logging.s3.amazonaws.com" }
Action = "s3:PutObject"
Resource = "${aws_s3_bucket.logs.arn}/*"
}]
})
}
resource "aws_s3_bucket_logging" "main" {
bucket = aws_s3_bucket.main.id
target_bucket = aws_s3_bucket.logs.id
target_prefix = "access-logs/"
# Logging fails if bucket policy isn't in place
depends_on = [aws_s3_bucket_policy.logs]
}resource "aws_vpc_endpoint" "s3" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.us-east-1.s3"
}
resource "aws_instance" "private" {
subnet_id = aws_subnet.private.id
ami = "ami-abc123"
instance_type = "t3.micro"
# Instance needs S3 endpoint to reach S3 from private subnet
depends_on = [aws_vpc_endpoint.s3]
user_data = <<-EOF
#!/bin/bash
aws s3 cp s3://my-bucket/config.tar.gz /tmp/
EOF
}module "networking" {
source = "./modules/networking"
}
module "database" {
source = "./modules/database"
vpc_id = module.networking.vpc_id # Implicit dep
subnet_ids = module.networking.subnet_ids # Implicit dep
}
module "app" {
source = "./modules/app"
vpc_id = module.networking.vpc_id
db_endpoint = module.database.endpoint
# Explicit: app needs database security group rules to be fully applied
depends_on = [module.database]
}resource "aws_security_group_rule" "ssh" {
type = "ingress"
security_group_id = aws_security_group.main.id
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["10.0.0.0/8"]
}
resource "aws_instance" "web" {
ami = "ami-abc123"
instance_type = "t3.micro"
# SSH provisioner needs the security group rule to be in place
depends_on = [aws_security_group_rule.ssh]
provisioner "remote-exec" {
inline = ["echo 'Connected!'"]
}
}# ❌ Unnecessary — the vpc_id reference already creates the dependency
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
depends_on = [aws_vpc.main] # REDUNDANT!
}
# ✅ The reference is enough
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
}# ❌ Don't chain unrelated resources
resource "aws_s3_bucket" "a" { bucket = "bucket-a" }
resource "aws_s3_bucket" "b" {
bucket = "bucket-b"
depends_on = [aws_s3_bucket.a] # WHY? These are independent
}
# ✅ Let Terraform create them in parallel
resource "aws_s3_bucket" "a" { bucket = "bucket-a" }
resource "aws_s3_bucket" "b" { bucket = "bucket-b" }When you add depends_on, Terraform:
depends_on = [
aws_iam_role_policy_attachment.lambda_s3,
aws_vpc_endpoint.s3,
]
# Multiple dependencies are supporteddata "aws_ami" "latest" {
most_recent = true
owners = ["self"]
filter {
name = "name"
values = ["my-app-*"]
}
# Wait for Packer build to complete before querying
depends_on = [null_resource.packer_build]
}Terraform's implicit dependencies (from resource references) handle 90% of ordering. Use depends_on only when there's a hidden dependency Terraform can't see: IAM propagation delays, bucket policies needed before logging, VPC endpoints before private resources, or module-level orchestration. If you find yourself adding depends_on everywhere, you're probably doing it wrong — check if a resource reference would create the dependency automatically.
Learn Terraform data sources to read existing AWS resources, look up AMIs, query remote state, and reference external information in your configurations.
Terraform for_each vs count explained with practical examples. Learn when to use each, how to migrate from count to for_each
Learn Terraform locals to reduce duplication, compute values, and organize complex configurations. Practical examples with naming conventions, tag maps
Understand Terraform variables (inputs) and outputs. Learn types, validation, defaults, sensitive values