How Terraform determines order
When you write:
resource "aws_iam_role" "task" {name = "ecs-task-role"
# ...
}
resource "aws_iam_role_policy" "task" {role = aws_iam_role.task.id # ← reference
# ...
}
Terraform sees the reference aws_iam_role.task.id and concludes that the policy depends on the role. It creates the role first, then the policy. This is called an implicit dependency. Terraform derived it from HCL automatically.
The dependency graph is built for the entire project. Resources that have no dependency on each other are created in parallel (up to 10 at a time by default).
When depends_on is needed
Sometimes a dependency exists but Terraform cannot see it. For example:
resource "aws_iam_role_policy_attachment" "lambda_logs" {role = aws_iam_role.lambda.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}
resource "aws_lambda_function" "my_func" {function_name = "my-function"
role = aws_iam_role.lambda.arn # reference to role, but NOT to policy_attachment
# Without depends_on, Lambda can be created before the attachment
# and fail with access denied on the first invocation.
depends_on = [aws_iam_role_policy_attachment.lambda_logs]
}
The Lambda references the role directly, not the attachment. Terraform has no way to know that Lambda needs those specific log permissions. Without depends_on, Lambda can be created before the permissions are attached and will fail on first execution.
Syntax
depends_on takes a list of resource addresses:
depends_on = [
aws_iam_role_policy_attachment.lambda_logs,
aws_iam_role_policy_attachment.lambda_sqs,
]
Addresses have no .id or .arn suffix; you reference the resource itself. No attributes after the name.
Can you use it in a data source?
Yes:
data "aws_iam_role" "existing" {name = "shared-role"
depends_on = [aws_iam_role.shared]
}
This is rare; a data source normally reads what already exists. But if a data source depends on a resource that Terraform must create first, depends_on helps.
Can you use it in a module?
Yes:
module "app" {source = "./modules/app"
# ...
depends_on = [aws_iam_role.app_runtime]
}
This applies when a module consumes a resource through a data source rather than through an input variable.
Pitfalls
-
depends_onis the last resort. If you can express the dependency through a reference, do that. A reference is more precise (Terraform understands not just the order but which attribute is used) and it shows up in the plan output. -
depends_ondoes not pass a value. It controls order only. If you need an attribute from a dependency, use a regular reference. -
depends_oncan hide an architectural problem. If nothing works without it, your resources may be too tightly coupled and the structure is worth revisiting. -
Too many
depends_onentries are a warning sign. One or two per project is normal. Twenty is a reason to pause. It often means too many unrelated things are bundled inside oneapply. -
depends_oncannot reference a specific attribute. Only the whole resource:aws_iam_role.lambda, notaws_iam_role.lambda.arn. This is by design; depends_on is about order, not about values. -
Terraform cannot influence cloud-side async behavior. If AWS creates a resource asynchronously internally (for example, an IAM role can take 5-10 seconds to propagate across a region), Terraform does not see that. It considers the role ready as soon as the API returns 200. A
time_sleepresource is sometimes added to work around this.