Skip to content
Infrastructure as Code iac resources 4 min read

depends_on

Terraform builds a dependency graph automatically by reading the references between your resources, then creates, updates, and destroys everything in the right order. But sometimes a dependency exists only at runtime and is invisible to Terraform — a resource needs another to be fully provisioned before it can work, yet there is no attribute connecting them in your configuration. The depends_on meta-argument lets you declare these hidden dependencies explicitly so Terraform schedules them correctly. It is a precision tool: reach for it only when an implicit reference is not possible.

How implicit dependencies normally work

When the argument of one resource references the attribute of another, Terraform infers an ordering edge automatically. This is the preferred way to express dependencies because the relationship is self-documenting and impossible to get out of sync.

resource "aws_iam_role" "lambda" {
  name = "report-generator"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect    = "Allow"
      Principal = { Service = "lambda.amazonaws.com" }
      Action    = "sts:AssumeRole"
    }]
  })
}

resource "aws_lambda_function" "report" {
  function_name = "report-generator"
  role          = aws_iam_role.lambda.arn # implicit dependency
  runtime       = "python3.12"
  handler       = "main.handler"
  filename      = "build/report.zip"
}

Because aws_lambda_function.report reads aws_iam_role.lambda.arn, Terraform knows the role must exist first. You do not need depends_on here, and adding it would be redundant.

When depends_on is actually needed

You need depends_on when a resource depends on the side effects of another resource that are never expressed as an attribute reference. A classic case: an IAM role policy attachment that must exist before a resource can use the role at runtime, even though the consuming resource only references the role itself — not the attachment.

resource "aws_iam_role_policy" "lambda_s3" {
  name = "s3-read"
  role = aws_iam_role.lambda.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect   = "Allow"
      Action   = ["s3:GetObject"]
      Resource = "${aws_s3_bucket.data.arn}/*"
    }]
  })
}

resource "aws_lambda_function" "report" {
  function_name = "report-generator"
  role          = aws_iam_role.lambda.arn
  runtime       = "python3.12"
  handler       = "main.handler"
  filename      = "build/report.zip"

  # The function reads from S3 at runtime. Without the policy attached,
  # the first invocation would fail with AccessDenied. Terraform cannot
  # infer this because nothing references aws_iam_role_policy.lambda_s3.
  depends_on = [aws_iam_role_policy.lambda_s3]
}

Here the Lambda function references the role’s ARN but never the policy. Without depends_on, Terraform might create the function before (or in parallel with) the policy, leaving a window where the function lacks permissions. The explicit dependency forces the policy to finish first.

Output:

aws_iam_role_policy.lambda_s3: Creating...
aws_iam_role_policy.lambda_s3: Creation complete after 1s
aws_lambda_function.report: Creating...
aws_lambda_function.report: Creation complete after 9s

Apply complete! Resources: 2 added, 0 changed, 0 destroyed.

Tip: depends_on only orders operations — it does not pass any data. If you find yourself wanting the value of another resource, use an attribute reference instead and delete the depends_on.

Syntax and accepted values

depends_on takes a list of references to entire resources, data sources, or modules — never to individual attributes.

resource "aws_instance" "app" {
  ami           = "ami-0abcdef1234567890"
  instance_type = "t3.micro"

  depends_on = [
    aws_iam_role_policy.lambda_s3, # another resource
    aws_s3_bucket.data,            # another resource
    module.networking,             # an entire module
  ]
}
Reference formValid in depends_on?Notes
aws_s3_bucket.dataYesReference to the whole resource
aws_s3_bucket.data.arnNoAttribute references are not allowed
module.networkingYesWaits for all resources in the module
data.aws_ami.ubuntuYesOrders against a data source read
var.regionNoVariables are not resources

depends_on on modules

When you call a module, depends_on makes every resource inside that module wait for the listed dependencies. This is useful when a module performs work that depends on infrastructure created outside it but has no input wired to express the link.

module "app_cluster" {
  source = "./modules/ecs-cluster"

  cluster_name = "production"

  # Ensure the shared VPC endpoints exist before the cluster pulls images.
  depends_on = [aws_vpc_endpoint.ecr]
}

Use this sparingly — it is a coarse hammer that serializes the whole module behind the dependency, which can slow applies.

Why to prefer implicit references

Explicit depends_on carries real costs. Terraform treats a resource with depends_on more conservatively: during planning it may report the resource and its dependents as needing changes because it cannot determine the dependency’s effect ahead of time. Overusing it also creates a graph that no longer reflects the actual data flow, making the configuration harder to reason about. Whenever you can express a dependency by referencing an attribute, do so.

Warning: depends_on is fully supported by OpenTofu with identical semantics, so configurations remain portable. Still, both tools emit the same conservative planning behavior, so do not scatter it across your modules to “be safe.”

Best practices

  • Default to implicit dependencies via attribute references; only add depends_on when no reference exists.
  • Use it for hidden runtime dependencies such as IAM policies, policy attachments, and service-linked roles that a resource needs but does not reference.
  • Reference whole resources, data sources, or modules — never bare attributes — or the configuration will fail to parse.
  • Keep the list minimal; each entry serializes execution and reduces parallelism.
  • Add a short comment explaining why the hidden dependency exists, since the link is not obvious from the code.
  • Prefer wiring a module input over depends_on on a module call when you can pass the dependency’s attribute through.
  • Re-evaluate depends_on during refactors; a once-hidden dependency may now be expressible as a clean reference.
Last updated June 14, 2026
Was this helpful?