Dependencies
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.
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.
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.
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 (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.
- Adding
depends_oneverywhere "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_onat 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.
- Express dependencies by referencing attributes and let the graph build itself; this should cover nearly everything.
- Reserve
depends_onfor 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 -destroyto confirm the reverse-order destroy is safe.
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_onblock to wire the edge by hand - They are simply declared one after another within the same
.tffile, 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