Cloud  /  Terraform

IaC Terraform 50 guides · updated 2026

Infrastructure as code done right — providers, state, reusable modules, and the workflow patterns that keep multi-cloud deployments sane in 2026.

depends_on in Terraform

Terraform automatically infers resource dependencies by analyzing attribute references between resources. depends_on is a meta-argument that adds explicit ordering constraints when Terraform can’t infer the dependency from configuration — typically when a resource has a hidden or indirect dependency that isn’t visible in resource attributes.


Implicit vs Explicit Dependencies

Terraform resolves most dependencies automatically:

# Implicit dependency — Terraform sees that aws_instance references aws_security_group
resource "aws_security_group" "web" {
name = "web-sg"
vpc_id = aws_vpc.main.id
}
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
vpc_security_group_ids = [aws_security_group.web.id] # Creates an implicit dependency
# Terraform knows: create aws_security_group.web before aws_instance.web
}

depends_on is needed only when Terraform can’t see the dependency:

# The IAM policy attachment affects what the instance can do,
# but the instance resource doesn't reference the attachment directly.
# Without depends_on, the instance might start before the role is fully attached.
resource "aws_iam_role_policy_attachment" "app" {
role = aws_iam_role.app.name
policy_arn = aws_iam_policy.app.arn
}
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
iam_instance_profile = aws_iam_instance_profile.app.name
# Explicit dependency — the instance's startup script needs the IAM permissions
depends_on = [aws_iam_role_policy_attachment.app]
}

Common depends_on Use Cases

IAM Permission Propagation

IAM changes in AWS propagate asynchronously. Resources that depend on IAM permissions might start before permissions are available:

resource "aws_iam_role" "lambda" {
name = "lambda-execution-role"
assume_role_policy = jsonencode({
Statement = [{
Effect = "Allow"
Principal = { Service = "lambda.amazonaws.com" }
Action = "sts:AssumeRole"
}]
})
}
resource "aws_iam_role_policy_attachment" "lambda_vpc" {
role = aws_iam_role.lambda.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
}
resource "aws_lambda_function" "api" {
filename = "lambda.zip"
function_name = "api-handler"
role = aws_iam_role.lambda.arn
# Wait for policy attachment — Lambda creation can fail if the role
# doesn't have VPC permissions yet when it's being created
depends_on = [aws_iam_role_policy_attachment.lambda_vpc]
}

Database Initialization Before Application

resource "aws_db_instance" "main" {
identifier = "app-db"
engine = "postgres"
instance_class = "db.t3.micro"
}
resource "null_resource" "run_migrations" {
provisioner "local-exec" {
command = "psql ${aws_db_instance.main.endpoint} < schema.sql"
}
}
resource "aws_ecs_service" "app" {
name = "app"
task_definition = aws_ecs_task_definition.app.arn
desired_count = 2
# Don't start the app until migrations have run
depends_on = [null_resource.run_migrations]
}

Security Group Before VPC Endpoint

resource "aws_security_group" "vpc_endpoint" {
name = "vpc-endpoint-sg"
vpc_id = aws_vpc.main.id
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
security_groups = [aws_security_group.app.id]
}
}
resource "aws_vpc_endpoint" "s3" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.us-east-1.s3"
vpc_endpoint_type = "Interface"
subnet_ids = aws_subnet.private[*].id
security_group_ids = [aws_security_group.vpc_endpoint.id]
depends_on = [aws_security_group.vpc_endpoint]
}

Module-Level depends_on

depends_on on a module block means: don’t start creating any resources in this module until all dependencies are ready.

module "networking" {
source = "./modules/networking"
cidr_block = "10.0.0.0/16"
}
module "application" {
source = "./modules/application"
vpc_id = module.networking.vpc_id
subnet_ids = module.networking.private_subnet_ids
# Even though vpc_id and subnet_ids create implicit dependencies,
# the application module also needs the NAT gateway to be ready
# (which isn't referenced directly)
depends_on = [module.networking]
}

What depends_on Does NOT Do

depends_on affects creation and destruction ordering only. It does not:

For health checks, use aws_lb_target_group_attachment waiters, or null_resource with polling scripts.


Avoid Overusing depends_on

Adding unnecessary depends_on declarations slows down Terraform by forcing sequential execution where parallelism is safe. Terraform applies resources in parallel by default — depends_on breaks that.

# BAD: unnecessary depends_on that forces sequential creation
resource "aws_s3_bucket" "assets" { ... }
resource "aws_s3_bucket" "logs" {
depends_on = [aws_s3_bucket.assets] # These are independent — no reason to sequence
}
# GOOD: leave independent resources without depends_on
resource "aws_s3_bucket" "assets" { ... }
resource "aws_s3_bucket" "logs" { ... } # Created in parallel with assets bucket

If you find yourself adding depends_on frequently, consider whether your resources are properly connected by attribute references — that’s Terraform’s preferred way to express ordering.