Dependencies
Terraform does not run your resources in file order — it builds an order from the references between them. When a Pub/Sub IAM member references google_service_account.processor.email, Terraform knows the service account must exist first; that reference is the dependency. You almost never declare ordering by hand, because the act of using one resource's value in another declares it for you.
Explicit depends_on exists for the minority of cases where a real ordering requirement leaves no reference to infer it from — most often an IAM binding that must be in place before the resource that uses that permission runs. It is a sharp tool with a narrow purpose, and over-using it is exactly how a clean, parallel graph turns into a slow tangle.
Implicit Dependencies from References
Any expression that reads another resource's attribute creates an edge. Write bucket = google_storage_bucket.raw.name and Terraform records that this resource depends on the bucket, and so the bucket is created first. This is the normal, preferred way ordering is established — you express what a resource needs by referencing it, and the order falls out for free.
resource "google_storage_notification" "raw" { bucket = google_storage_bucket.raw.name # edge: bucket before notification topic = google_pubsub_topic.events_ingest.id }
Two edges are declared here, both implicitly: the notification needs the hatch-events-raw bucket and the events-ingest topic, so both are created before it. You wrote no ordering instruction — the references carry it.
When There Is No Reference to Infer From
Sometimes A must exist before B, but B's configuration never reads anything from A. The classic GCP case is an IAM binding: the event-processor Cloud Run service must run with a service account that already holds its BigQuery write role, but the Cloud Run block references neither the binding nor the role — it references the service account's email, which exists whether or not the role is attached.
So there is no implicit edge from the binding to the service, even though one is genuinely needed. Terraform may create the service before the role is granted, the service starts, and its first write to analytics returns permission denied. Nothing in the config told Terraform to wait, because no value flowed from the binding into the service.
depends_on for Hidden Dependencies
This is what depends_on is for. Adding depends_on = [google_project_iam_member.processor_bq] to the Cloud Run service forces it to be created only after the service account has its BigQuery write role — supplying the ordering edge that no reference could express.
resource "google_cloud_run_v2_service" "processor" { name = "event-processor" # ... config that references the SA email but not its roles ... depends_on = [google_project_iam_member.processor_bq] }
The rule of thumb: use depends_on when there is a real ordering requirement and no attribute of the dependency appears anywhere in the dependent's config. If a reference exists, the edge already exists and depends_on is noise.
API Enablement and Eventual Consistency
A resource often needs an API enabled before it can be created — a Cloud Run service needs the Run API on. You express that with depends_on the relevant google_project_service. But because API enablement on GCP is eventually consistent, the edge alone is sometimes not enough: enablement is acknowledged before it has fully propagated, and the next resource can still race it.
The pragmatic fix where this race actually appears is a short time_sleep between the enablement and the dependent resource, giving propagation a window to finish. Add it only where the race shows up in practice, not preemptively on every API — a sleep on every enablement slows every apply for a problem most of them do not have.
Over-Using depends_on
Adding depends_on "to be safe" when a reference already exists is redundant noise — it obscures the real inferred edge and implies a problem that is not there. Worse is a broad depends_on: putting it on an entire module, or between two resources that did not need ordering, serializes more than necessary. Every resource that now waits has lost its chance to run in parallel, and the apply slows for no benefit. Scope every depends_on to the single resource that genuinely must wait.
- Creating
event-processorwith a service account that lacks its BigQuery role at create time because no reference forced the ordering — the service starts, the first write toanalyticsreturns permission denied, and the failure is intermittent and confusing. - Adding
depends_onto a resource that already references the dependency's attribute — pure noise that obscures the real, inferred edge and suggests a problem that is not there. - Using
depends_onto paper over a missing reference instead of just referencing the attribute — you lose the value the reference would have carried and keep a brittle manual edge. - Putting
depends_onon an entire module when only one resource inside needs to wait — every resource in the module now waits, serializing the apply and erasing parallelism. - Assuming
depends_on google_project_service.runalone guarantees the API is usable — enablement propagates asynchronously, so the next resource can still race it without a brieftime_sleep. - Wiring a literal GCP name into a dependent resource instead of referencing the attribute — the literal carries the value but creates no edge, so the ordering you assumed never exists.
- Express dependencies by referencing attributes; let Terraform infer order and reserve
depends_onfor genuinely hidden requirements. - Use
depends_onfor the IAM-before-use case, where a resource needs a permission granted by a binding it does not reference. - Scope
depends_onto the narrowest resource that actually must wait, never a whole module, to preserve parallelism. - Pair
depends_onon agoogle_project_servicewith a shorttime_sleeponly where an enablement race actually appears, not preemptively everywhere. - Audit existing
depends_onlines periodically and delete any whose dependency is already expressed by a reference.
dependsOn identically
Config Connector relies on the Kubernetes controller's continuous reconciliation, not an ordered graph
Ansible orders by task position — the opposite model
Knowledge Check
How does Terraform learn that the bucket must be created before the notification that points at it?
- The notification references
google_storage_bucket.raw.name, and that reference creates the ordering edge - It reads the two resource blocks top to bottom as they appear in the file and creates them in that written order
- Both resource blocks carry an explicit
depends_onthat lists the other one - The provider hardcodes that storage buckets always precede their notifications
Why does the IAM-before-use case need an explicit depends_on?
- The service references the account's email but never the role binding, so no reference creates the required edge
- IAM role bindings are always applied dead last in the run, after every other resource, unless something forces them earlier
- A Cloud Run service is forbidden from referencing any other resource by attribute
- The role binding and the service are declared in two different modules
What is the cost of putting depends_on on an entire module instead of one resource?
- Every resource in the module now waits, serializing the apply and erasing parallelism
- The whole module is destroyed and recreated from scratch on every apply
- Terraform refuses to run because a module-level
depends_onis rejected outright as invalid syntax - The module's outputs become
(known after apply)permanently on every run
Why might depends_on on a google_project_service still let the next resource fail?
- API enablement is eventually consistent and is acknowledged before it propagates; a short
time_sleepcovers the gap depends_onis silently unsupported and quietly ignored on thegoogle_project_serviceresource type, so the ordering never takes hold- Enabling the API requires a second, separate apply before it takes effect
- The dependent resource quietly ignores its
depends_onedge at apply time
You got correct