Chapter 2: The Core Workflow
Topic 11

Dependencies

Ordering

Terraform decides what order to create and destroy resources by building a dependency graph, and most of that graph comes for free. When resource A references resource B's attribute, Terraform knows B must exist before A and orders them automatically. You almost never declare ordering by hand — you express it as data flow, and the order falls out.

The exception is depends_on, the explicit escape hatch for ordering Terraform cannot see from any reference. It is genuinely necessary in a narrow set of cases and a code smell everywhere else — over-using it is one of the most common ways teams accidentally slow their applies and obscure their real architecture.

Two ways to declare ordering
Implicit
Reference-based — created automatically when one resource reads another's attribute. Precise, self-maintaining, expresses ~95% of all ordering.
Explicit
A depends_on override for ordering no reference carries. Blunt: it waits on the whole resource and serializes work. Use it sparingly, and comment why.

Implicit Dependencies

An implicit dependency is created the instant one resource references another's attribute. The subnet below reads aws_vpc.main.id, so Terraform records an edge: the VPC must be created before the subnet, and destroyed after it. You wrote no ordering instruction — the reference is the instruction. This is how roughly 95% of all ordering in a real configuration is expressed, and it is self-maintaining, because the edge follows the actual data and disappears the moment the reference does.

An implicit dependency from a reference
resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"
}

resource "aws_subnet" "app" {
  vpc_id     = aws_vpc.main.id   # VPC before subnet, automatically
  cidr_block = "10.0.1.0/24"
}

Explicit Dependencies

depends_on declares an ordering that no attribute reference expresses. The classic case is permission ordering: an EC2 instance assumes an IAM role, but nothing in the instance's arguments references the policy attachment that grants the role its permissions. The dependency is real — the instance's startup script will fail if it runs before the policy exists — but it is invisible to Terraform because no data flows between the two blocks. depends_on takes a list of resource or module addresses, never an attribute value.

An explicit dependency for permission ordering
resource "aws_iam_role_policy_attachment" "app" {
  role       = aws_iam_role.app.name
  policy_arn = aws_iam_policy.app.arn
}

resource "aws_instance" "app" {
  ami                  = data.aws_ami.al2023.id
  instance_type        = "t3.micro"
  iam_instance_profile = aws_iam_instance_profile.app.name

  # the policy must attach before the instance boots and uses the role
  depends_on = [aws_iam_role_policy_attachment.app]
}

When depends_on Is Needed

The legitimate uses share a shape: the dependency is real but not carried by any data. Permission ordering is the most common — an action needs an IAM policy that the resource's arguments do not reference. Eventual-consistency cases are the other — an AWS API reports a resource as created before it is fully usable, and a dependent resource fails intermittently if it proceeds too soon. In both, no attribute connects the two resources, so Terraform has nothing to infer the order from, and depends_on supplies it.

The Cost of Over-using depends_on

depends_on is blunt in two ways. It depends on the entire target resource rather than one attribute, so it waits for the whole thing even when a single value would have sufficed. And it forces serialization that Terraform would otherwise have parallelized — two unrelated resources that could have built at the same time now run one after the other. Sprinkled "to be safe," it quietly turns a fast parallel apply into a slow sequential one and hides the real data relationships that should have been references instead.

Dependencies and Destroy Order

Terraform destroys in reverse dependency order: if the subnet depends on the VPC, the subnet is destroyed first, then the VPC. Getting dependencies right is therefore not only about safe creation — it is what makes teardown safe too. A missing or wrong dependency can make a destroy fail (trying to delete a VPC that still has a subnet) or hang. On a complex stack, run terraform plan -destroy to see the teardown order before you trust it.

Implicit vs Explicit dependencies

Implicit (reference-based) — created automatically when one resource references another's attribute. Precise, self-maintaining, and follows real data flow. This should express nearly every dependency you have.

Explicit (depends_on) — a manual ordering override for dependencies no attribute reference expresses, such as IAM permission ordering or eventual consistency. Blunt: it depends on the whole resource and can serialize work. Use it only when a genuine ordering requirement has no data path, and comment why.

Common Mistakes
  • Adding depends_on everywhere "to be safe," serializing the graph and slowing applies while hiding the real data relationships that should be references.
  • Hardcoding an ID instead of referencing the attribute, which removes the implicit dependency and lets Terraform create things in the wrong order.
  • Missing a real permission-ordering dependency — an instance boots before its IAM policy attaches — and hitting intermittent startup failures that are hard to reproduce.
  • Pointing depends_on at an attribute value instead of a resource address; it takes resource and module addresses, not values.
  • Trusting destroy order on a complex stack without checking it, then watching a teardown fail because a wrong dependency leaves a resource with children.
Best Practices
  • Express dependencies by referencing attributes and let the graph build itself; this should cover nearly everything.
  • Reserve depends_on for genuine non-data ordering — IAM permissions, eventual consistency — and comment why each one exists.
  • When tempted to add depends_on, first check whether a missing attribute reference is the real fix.
  • Pass resource or module addresses to depends_on, never attribute values.
  • Verify teardown on complex stacks with terraform plan -destroy to confirm the reverse-order destroy is safe.
Comparable tools CloudFormation infers from Ref and GetAtt, offers DependsOn Pulumi infers from references, offers dependsOn Ansible no direct equivalent — ordering is the literal task order

Knowledge Check

How is an implicit dependency between two resources created?

  • One resource references another's attribute, and Terraform records the ordering edge automatically
  • You list both resources together inside a shared depends_on block to wire the edge by hand
  • They are simply declared one after another within the same .tf file, following strict top-to-bottom source order
  • Terraform creates one between every pair of resources by default

When is depends_on genuinely required rather than a smell?

  • When a real ordering requirement exists but no attribute reference carries it, such as IAM permission ordering
  • Whenever two resources happen to be created together in the same apply run, to guarantee their ordering is correct
  • Whenever one resource is larger or more expensive than another
  • Any time you reference another resource's attribute, to make the dependency explicit

Why does over-using depends_on hurt?

  • It is coarse and serializes work Terraform could have run in parallel, while hiding the real data relationships
  • It silently encrypts the attributes of every dependent resource as those values are written into the persisted state file
  • It permanently prevents Terraform from ever destroying the resources tied together by the edge
  • It forces a full refresh of every resource on each plan

In what order does Terraform destroy resources?

  • Reverse dependency order — dependents are destroyed before the things they depend on
  • The exact order they happen to appear in the configuration files, read strictly top to bottom
  • The very same forward order in which it originally created them
  • Alphabetical by resource address

You got correct