One of Terraform's core strengths is its ability to discover relationships between resources and apply infrastructure changes in a safe, predictable order. Rather than executing configuration files line by line, Terraform analyzes the entire configuration, builds a dependency graph, and determines how resources should be created, updated, or destroyed. This approach allows Terraform to manage complex infrastructure while minimizing the risk of outages or unintended side effects.
In most cases, Terraform handles dependency and lifecycle management automatically. By analyzing links between resources, Terraform infers dependencies and ensures that resources are provisioned or modified in the correct sequence. It also determines whether a change can be applied in place or requires a resource to be replaced. For many configurations, such default behavior is sufficient and requires little to no additional input from the user.
However, real-world infrastructure often introduces scenarios where explicit control over resource lifecycle and dependencies becomes necessary. Examples include enforcing zero-downtime replacements, protecting critical resources from accidental deletion, tolerating externally managed changes, or coordinating updates across loosely coupled components. To address these needs, Terraform provides a set of lifecycle and dependency management features that allow users to fine-tune how changes are planned and applied.
Terraform's lifecycle and dependency management capabilities include:
depends_on meta-argument when
relationships cannot be inferred automaticallycreate_before_destroy for safer replacementsprevent_destroy to protect critical resourcesignore_changes to tolerate controlled configuration driftreplace_triggered_by to coordinate resource replacements-target)-replace)-refresh-only)These features are essential for designing Terraform configurations that are both flexible and safe. The sections that follow explore Terraform's default behavior in more detail, then examine each lifecycle and dependency mechanism, along with practical use cases where explicit control is required.
Most Terraform configurations do not require explicit dependency or lifecycle settings. By default, Terraform analyzes the configuration and determines how resources relate to one another, how changes should be applied, and in what order operations must occur. This behavior is driven by Terraform's dependency graph, which is built during the planning phase.
Dependency Graph and Implicit Dependencies
When terraform plan is executed, Terraform constructs a directed acyclic
graph (DAG) of all resources and data sources in the configuration. Each node in the graph
represents a resource, and each edge represents a dependency. Terraform uses this graph to
determine creation order, update order, and safe deletion sequences.
Dependencies are inferred implicitly through attribute references. When one resource references an attribute of another resource, Terraform automatically ensures that the referenced resource is created or updated first. For example, a compute instance that references a subnet ID will not be created until the subnet exists. This implicit dependency mechanism is the primary way Terraform manages ordering and is both reliable and expressive.
Because of this design, the arrangement of resources in configuration files has no effect on execution order. Terraform relies entirely on the dependency graph rather than file structure or declaration order.
Parallelism and Ordering
Terraform executes operations in parallel whenever possible. Resources that do not depend on one another may be created, updated, or destroyed simultaneously. This parallelism improves performance while still preserving correctness through dependency enforcement.
The default number of concurrent resource operations is 10 and can be changed with the
-parallelism CLI option.
When dependencies exist, Terraform strictly honors them. A resource will not be modified or destroyed until all dependent resources are in a safe state. During destruction, Terraform reverses the dependency order, ensuring that dependent resources are removed before the resources they rely on.
Default Resource Lifecycle Behavior
For each resource change, Terraform determines whether the change can be applied in place or whether the resource must be replaced. This decision is primarily driven by the provider and the resource type.
By default:
Terraform presents these decisions clearly in the plan output, using actions such as
+ create, ~ update in-place, - destroy,
+/- create replacement and then destroy, or
-/+ destroy and then create replacement.
State, Drift Detection, and Refresh
Terraform's default lifecycle behavior is tightly coupled with the state file. Before planning changes, Terraform refreshes the state by querying the providers and comparing the real infrastructure with the recorded state. Any differences are identified as drift and factored into the plan.
If a resource differs from the configuration, Terraform attempts to reconcile the difference by applying the desired configuration. This ensures that the declared configuration remains the single source of truth, unless explicitly instructed otherwise.
Why Defaults Are Usually Enough
Terraform's default dependency and lifecycle behavior is sufficient for the majority of use cases. Implicit dependencies reduce configuration complexity, parallel execution improves efficiency, and provider-driven lifecycle decisions handle most infrastructure changes safely.
However, some scenarios, such as preventing accidental deletions, enforcing zero-downtime replacements, or managing resources with external side effects require behavior that deviates from these defaults. Understanding Terraform's baseline behavior is important before introducing explicit lifecycle rules or dependency overrides, which are covered in the sections that follow.
Terraform lifecycle management features allow you to control how individual resources are created, updated, replaced, and destroyed. While Terraform's default behavior is safe and efficient for most scenarios, lifecycle meta-arguments provide precise control when infrastructure changes must follow stricter rules, such as avoiding downtime, protecting critical resources, or tolerating controlled drift.
Lifecycle settings are defined within a resource's lifecycle block and apply
only to that resource. They do not change Terraform's dependency graph directly, but they
strongly influence how Terraform plans and executes changes for that resource.
Resource Lifecycle Meta-Arguments Overview
Terraform supports the following lifecycle meta-arguments:
create_before_destroyprevent_destroyignore_changesreplace_triggered_byEach of these addresses a specific class of operational or safety concerns and should be used deliberately.
create_before_destroy
By default, when a resource must be replaced, Terraform destroys the existing resource before creating the new one. In environments that require high availability, this behavior can cause outages or service interruptions.
The create_before_destroy meta-argument reverses this behavior by instructing
Terraform to create the replacement resource first and destroy the old one only after the
new resource is successfully provisioned.
Common use cases include:
Important considerations:
prevent_destroy
The prevent_destroy meta-argument acts as a safeguard against accidental
deletions. When it is set to true, Terraform will fail the plan or apply
if an operation would destroy the resource, whether directly or as part of a
replacement.
Typical use cases include:
Key behaviors:
prevent_destroy lifecycle rule for
resource modificationThis feature is particularly useful in shared environments or production workspaces where destructive changes carry high risk.
ignore_changes
The ignore_changes meta-argument tells Terraform to disregard differences
between the configuration and the actual resource for specific attributes (or all
attributes). This allows Terraform to tolerate controlled drift without constantly attempting
to revert changes.
Common scenarios include:
Behavioral notes:
Used carefully, ignore_changes helps balance Terraform's declarative model
with operational realities.
replace_triggered_by
The replace_triggered_by meta-argument allows you to force resource replacement
when another resource or its attribute changes, even if Terraform would not normally replace the
resource.
This is useful when:
Examples include:
Unlike depends_on, this feature influences replacement behavior rather than execution
order.
When to Use Lifecycle Meta-Arguments
Lifecycle meta-arguments are precision tools, not general-purpose configuration options. They are most effective when:
Before applying lifecycle overrides, it is important to understand Terraform's default behavior and verify that the chosen approach aligns with provider capabilities and operational constraints.
Terraform dependency management determines the order in which resources are created, updated, and destroyed. Unlike lifecycle controls, which affect how or when individual resources change, dependency management governs how resources relate to one another across the entire configuration. Terraform's ability to infer and enforce dependencies is central to its declarative model and is one of the primary reasons most configurations require little explicit ordering logic.
Terraform tracks dependencies using a graph-based model and supports both implicit and explicit dependency definitions. Understanding how and when to use each approach is critical to writing maintainable and predictable Terraform configurations.
Implicit Dependencies
Implicit dependencies are the default and preferred mechanism for dependency tracking in Terraform. They are created automatically whenever a resource references an attribute of another resource or data source.
For example, if a compute resource references a subnet ID, Terraform automatically ensures that the subnet is created before the compute resource. No additional configuration is required.
Key characteristics of implicit dependencies:
Implicit dependencies also apply to data sources. If a resource references a data source that queries infrastructure, Terraform ensures the data source is evaluated before the resource is deployed.
Because implicit dependencies are embedded directly in configuration logic, they provide both correct ordering and clear representation of resource relationships.
Explicit Dependencies with depends_on
depends_on can be used in the following Terraform configuration blocks:
check blocksdata blocksephemeral blocksmodule blocksoutput blocksresource blocks
The depends_on meta-argument allows you to define an explicit dependency
in situations where Terraform cannot infer it automatically. This is typically necessary when a
dependency exists due to side effects or external behavior that is not captured through
attribute references.
Common use cases include:
Unlike implicit dependencies, depends_on does not rely on attribute references.
Instead, it enforces a strict ordering relationship between resources or modules.
Important considerations:
depends_on can obscure true resource relationships
As a best practice, depends_on should be treated as an exception rather than
a default pattern.
Module-Level Dependencies
Dependencies can also exist at the module level. When outputs from one module are consumed by another, Terraform automatically creates implicit dependencies between the modules.
In cases where module interactions rely on side effects or external processes, depends_on
can be applied at the module block level to enforce ordering. This is particularly useful in
larger, layered architectures where responsibilities are separated across modules.
Module-level dependency management helps Terraform orchestrate complex infrastructure without requiring tightly coupled configurations.
Dependency Behavior During Destruction
Terraform applies dependencies in reverse during destruction. Resources that depend on others are destroyed first, ensuring that dependent infrastructure is removed safely before shared or foundational components.
For example:
This reverse-order destruction is automatic and requires no additional configuration, provided dependencies are correctly expressed.
Best Practices for Dependency Management
depends_on only when dependencies cannot be expressed through attributes-target) in regular workflowsTerraform's dependency management model is intentionally opinionated. By aligning configurations with its implicit dependency system, you gain safer plans, better parallelism, and more maintainable infrastructure definitions.
In addition to configuration-based lifecycle rules, Terraform provides several command-line options that can influence how resources are created, updated, replaced, or destroyed. These CLI-driven controls operate outside the regular workflow and are typically used for exception handling, recovery, or surgical changes, rather than as part of normal operations.
Because these options can partially bypass Terraform's dependency graph or override planned behavior, they should be used deliberately and with a clear understanding of their impact.
Targeted Operations (-target)
The -target option instructs Terraform to plan and apply changes for a specific
resource (or set of resources), rather than the entire configuration.
Typical use cases include:
Important characteristics:
Because -target bypasses parts of the dependency graph, it can leave infrastructure
in an incomplete or transitional state. For this reason, it is best used as a short-term
corrective tool rather than a standard deployment mechanism.
Forced Resource Replacement (-replace)
The -replace option explicitly marks a resource for replacement during the next
apply, even if Terraform would normally plan no actions on the resource.
Common scenarios include:
Key behaviors:
Unlike -target, -replace does not limit the scope
of the plan, it simply forces a replacement decision for the specified resource.
Refresh-Only Operations (-refresh-only)
The -refresh-only option updates Terraform state to reflect the current
real-world infrastructure without proposing or applying changes.
This is useful when:
Refresh-only operations:
While refresh operations are typically automatic, -refresh-only allows
explicit control when diagnosing state discrepancies.
Destructive Operations (terraform destroy)
The terraform destroy command is a lifecycle-level operation that
removes all managed resources in a configuration or workspace.
Important notes:
prevent_destroy are enforced-target during applyThis command is typically used for ephemeral environments, testing, or controlled teardown scenarios.
Legacy Resource Tainting (terraform taint)
Historically, Terraform supported resource tainting as a way to mark a resource for replacement during the next apply. A tainted resource is treated as unhealthy, causing Terraform to destroy and recreate it even if no configuration changes are detected.
Key points:
However, terraform taint is now deprecated and has been superseded by the
-replace option. The modern approach provides clearer intent, improved plan
visibility, and better integration with standard workflows.
As a result, terraform taint should be avoided in new automation and used
only when maintaining or migrating existing legacy workflows.
When to Use CLI Lifecycle Controls
CLI-driven lifecycle controls are most appropriate when:
Terraform's CLI provides powerful tools, but they should not replace configuration-based lifecycle management for repeatable or long-term behavior. Whenever possible, lifecycle intent should be captured in code rather than enforced manually via CLI options.
While Terraform's default lifecycle and dependency behavior works well for most configurations, certain scenarios require explicit lifecycle or dependency controls. These situations typically involve safety, ordering, controlled drift, or coordinated changes that Terraform cannot infer automatically.
This section introduces several common use cases and illustrates them with simple examples that highlight Terraform's lifecycle management behavior.
create_before_destroy
Problem:
By default, Terraform destroys a resource before creating its replacement. For some critical resources
this may cause temporary unavailability.
Solution:
Use create_before_destroy to ensure the replacement is created first.
Example:
Workflow:
Initialize and apply the configuration:
terraform init terraform apply -auto-approve
Force re-creation of random_pet.pet_cbd:
terraform apply -auto-approve -replace=random_pet.pet_cbd
random_pet.pet_cbd: Refreshing state... [id=needed-bee]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement
Terraform will perform the following actions:
# random_pet.pet_cbd will be replaced, as requested
-/+ resource "random_pet" "pet_cbd" {
~ id = "needed-bee" -> (known after apply)
# (2 unchanged attributes hidden)
}
Plan: 1 to add, 0 to change, 1 to destroy.
random_pet.pet_cbd: Destroying... [id=needed-bee]
random_pet.pet_cbd: Destruction complete after 0s
random_pet.pet_cbd: Creating...
random_pet.pet_cbd: Creation complete after 0s [id=learning-hookworm]
Apply complete! Resources: 1 added, 0 changed, 1 destroyed.
Note the Terraform's default replacement behavior:
-/+ destroy and then create replacement.
Uncomment the line with create_before_destroy and re-run terraform apply:
terraform apply -auto-approve -replace=random_pet.pet_cbd
random_pet.pet_cbd: Refreshing state... [id=learning-hookworm]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+/- create replacement and then destroy
Terraform will perform the following actions:
# random_pet.pet_cbd will be replaced, as requested
+/- resource "random_pet" "pet_cbd" {
~ id = "learning-hookworm" -> (known after apply)
# (2 unchanged attributes hidden)
}
Plan: 1 to add, 0 to change, 1 to destroy.
random_pet.pet_cbd: Creating...
random_pet.pet_cbd: Creation complete after 0s [id=legible-rhino]
random_pet.pet_cbd (deposed object e145d3a4): Destroying... [id=learning-hookworm]
random_pet.pet_cbd: Destruction complete after 0s
Apply complete! Resources: 1 added, 0 changed, 1 destroyed.
Note the change in the Terraform's replacement behavior:
+/- create replacement and then destroy.
Key takeaway:
create_before_destroy helps avoid temporary unavailability by ensuring that a
replacement resource is created before the existing one is removed. However, the actual
replacement behavior depends on the resource type and provider capabilities. Some resources
cannot safely coexist in duplicate form due to naming constraints, uniqueness requirements,
or other limitations. In such cases, the apply may fail or produce an unexpected result
unless additional design considerations are introduced.
prevent_destroy
Problem:
Some critical resources must be protected from accidental deletion or re-creation.
Solution:
Use prevent_destroy to block destructive operations.
Example:
Workflow:
Initialize and apply the configuration:
terraform init terraform apply -auto-approve
Re-apply with an updated content_pd value:
terraform apply -auto-approve -var "content_pd=version-1.1"
local_file.file_pd: Refreshing state... [id=f1af4003baac597259e2d2b420470de7ce76b9e6]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement
Terraform planned the following actions, but then encountered a problem:
# local_file.file_pd must be replaced
-/+ resource "local_file" "file_pd" {
~ content = "version=1.0" -> "version-1.1" # forces replacement
...
~ id = "f1af4003baac597259e2d2b420470de7ce76b9e6" -> (known after apply)
# (3 unchanged attributes hidden)
}
Plan: 1 to add, 0 to change, 1 to destroy.
╷
│ Error: Instance cannot be destroyed
│
│ on lifecycle_pd.tf line 25:
│ 25: resource "local_file" "file_pd" {
│
│ Resource local_file.file_pd has lifecycle.prevent_destroy set, but the plan calls for this resource to be destroyed. To avoid this error and continue with the plan, either disable
│ lifecycle.prevent_destroy or reduce the scope of the plan using the -target option.
Any configuration changes that need to destroy the resource either to replace it or as part of the
terraform destroy workflow will fail with the Instance cannot be destroyed
error. Terraform requires explicit removal of the prevent_destroy lifecycle rule to
proceed.
Key takeaway:
prevent_destroy acts as a safety lock for critical or long-lived resources.
ignore_changes
Problem:
Some resource attributes may be modified externally or change frequently, causing Terraform to
detect drift and attempt unnecessary updates.
Solution:
Use ignore_changes to exclude specific attributes from diff calculations.
Example:
Workflow:
Initialize and apply the configuration:
terraform init terraform apply -auto-approve
Change the bucket's default storage class using Cloud Console or gcloud CLI:
gcloud storage buckets update \ gs://<bucket_name> \ --default-storage-class=NEARLINE
Refresh Terraform state and inspect bucket's configuration:
terraform apply -refresh-only -auto-approve
Uncomment the labels = {type = "temp"} line and re-run
terraform apply.
terraform apply -auto-approve
google_storage_bucket.example: Refreshing state... [id=tf-demo-bucket-760114-01]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
~ update in-place
Terraform will perform the following actions:
# google_storage_bucket.example will be updated in-place
~ resource "google_storage_bucket" "example" {
~ effective_labels = {
+ "type" = "temp"
# (1 unchanged element hidden)
}
id = "tf-demo-bucket-760114-01"
~ labels = {
+ "type" = "temp"
}
name = "tf-demo-bucket-760114-01"
~ terraform_labels = {
+ "type" = "temp"
# (1 unchanged element hidden)
}
# (15 unchanged attributes hidden)
# (2 unchanged blocks hidden)
}
Plan: 0 to add, 1 to change, 0 to destroy.
google_storage_bucket.example: Modifying... [id=tf-demo-bucket-760114-01]
google_storage_bucket.example: Modifications complete after 0s [id=tf-demo-bucket-760114-01]
Apply complete! Resources: 0 added, 1 changed, 0 destroyed.
Terraform ignores manual changes to the storage_class attribute
and continues to manage all other bucket parameters. The bucket's default storage class
remains NEARLINE.
Key takeaway:
ignore_changes allows Terraform to coexist with external resource management
systems, but should be used sparingly.
replace_triggered_by
Problem:
Sometimes a resource must be replaced when another resource changes, even if there is no
direct attribute dependency.
Solution:
Use replace_triggered_by to explicitly define replacement behavior.
Example:
Workflow:
Initialize and apply the configuration:
terraform init terraform apply -auto-approve
Force re-creation of random_id.token_rtb:
terraform apply -auto-approve -replace=random_id.token_rtb
Note that replacement of random_id.token_rtb does not affect
random_pet.pet_rtb.
Uncomment the line with replace_triggered_by and re-run terraform apply:
terraform apply -auto-approve -replace=random_id.token_rtb
random_id.token_rtb: Refreshing state... [id=DuDT-Q]
random_pet.pet_rtb: Refreshing state... [id=sharp-deer]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement
Terraform will perform the following actions:
# random_id.token_rtb will be replaced, as requested
-/+ resource "random_id" "token_rtb" {
~ b64_std = "DuDT+Q==" -> (known after apply)
~ b64_url = "DuDT-Q" -> (known after apply)
~ dec = "249615353" -> (known after apply)
~ hex = "0ee0d3f9" -> (known after apply)
~ id = "DuDT-Q" -> (known after apply)
# (1 unchanged attribute hidden)
}
# random_pet.pet_rtb will be replaced due to changes in replace_triggered_by
-/+ resource "random_pet" "pet_rtb" {
~ id = "sharp-deer" -> (known after apply)
# (2 unchanged attributes hidden)
}
Plan: 2 to add, 0 to change, 2 to destroy.
random_pet.pet_rtb: Destroying... [id=sharp-deer]
random_pet.pet_rtb: Destruction complete after 0s
random_id.token_rtb: Destroying... [id=DuDT-Q]
random_id.token_rtb: Destruction complete after 0s
random_id.token_rtb: Creating...
random_id.token_rtb: Creation complete after 0s [id=VMCsuQ]
random_pet.pet_rtb: Creating...
random_pet.pet_rtb: Creation complete after 0s [id=topical-kid]
Apply complete! Resources: 2 added, 0 changed, 2 destroyed.
When random_id.token_rtb changes, Terraform also forces replacement of
random_pet.pet_rtb due to the link defined with the
replace_triggered_by argument.
Key takeaway:
replace_triggered_by enables coordinated changes across loosely coupled resources.
depends_on
Problem:
Terraform cannot always infer dependencies when relationships are based on side effects rather than
attribute references.
Solution:
Use depends_on to enforce ordering.
Example:
Workflow:
Initialize and apply the configuration:
terraform init
terraform apply -auto-approve
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# local_file.file_do will be created
+ resource "local_file" "file_do" {
+ content = (known after apply)
...
+ filename = "/home/user/file_do.txt"
+ id = (known after apply)
}
# random_id.token_do will be created
+ resource "random_id" "token_do" {
+ b64_std = (known after apply)
...
+ id = (known after apply)
}
# terraform_data.file_do will be created
+ resource "terraform_data" "file_do" {
+ id = (known after apply)
}
Plan: 3 to add, 0 to change, 0 to destroy.
terraform_data.file_do: Creating...
terraform_data.file_do: Provisioning with 'local-exec'...
terraform_data.file_do (local-exec): Executing: ["/bin/sh" "-c" "(echo -n 'File content: ' && cat /home/user/file_do.txt) || echo 'File does not exists!'"]
random_id.token_do: Creating...
random_id.token_do: Provisioning with 'local-exec'...
random_id.token_do (local-exec): Executing: ["/bin/sh" "-c" "echo 'Sleeping 20 sec' && sleep 20"]
random_id.token_do (local-exec): Sleeping 20 sec
terraform_data.file_do (local-exec): File content: cat: /home/user/file_do.txt: No such file or directory
terraform_data.file_do (local-exec): File does not exists!
terraform_data.file_do: Creation complete after 0s [id=ca6a51fb-a724-3e99-3d41-d0a11f3fafbe]
random_id.token_do: Still creating... [00m10s elapsed]
random_id.token_do: Still creating... [00m20s elapsed]
random_id.token_do: Creation complete after 20s [id=b0oE8Q]
local_file.file_do: Creating...
local_file.file_do: Creation complete after 0s [id=4936512e550300ed01d468cd8b7b699d6c3e8507]
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
Note the No such file... error reported by terraform_data.file_do (local-exec).
Terraform attempts to provision resources in parallel and since there is no implicit
("visible" to Terraform) dependency between terraform_data.file_do and
local_file.file_do, terraform_data.file_do is provisioned before the
local file file_do.txt is created.
Uncomment the line with depends_on and re-run terraform apply:
terraform apply -auto-approve \
-replace=terraform_data.file_do \
-replace=random_id.token_do
random_id.token_do: Refreshing state... [id=b0oE8Q]
local_file.file_do: Refreshing state... [id=4936512e550300ed01d468cd8b7b699d6c3e8507]
terraform_data.file_do: Refreshing state... [id=ca6a51fb-a724-3e99-3d41-d0a11f3fafbe]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement
Terraform will perform the following actions:
# local_file.file_do must be replaced
-/+ resource "local_file" "file_do" {
~ content = "b0oE8Q" -> (known after apply) # forces replacement
...
~ id = "4936512e550300ed01d468cd8b7b699d6c3e8507" -> (known after apply)
# (3 unchanged attributes hidden)
}
# random_id.token_do will be replaced, as requested
-/+ resource "random_id" "token_do" {
~ b64_std = "b0oE8Q==" -> (known after apply)
...
~ id = "b0oE8Q" -> (known after apply)
# (1 unchanged attribute hidden)
}
# terraform_data.file_do will be replaced, as requested
-/+ resource "terraform_data" "file_do" {
~ id = "ca6a51fb-a724-3e99-3d41-d0a11f3fafbe" -> (known after apply)
}
Plan: 3 to add, 0 to change, 3 to destroy.
terraform_data.file_do: Destroying... [id=ca6a51fb-a724-3e99-3d41-d0a11f3fafbe]
terraform_data.file_do: Destruction complete after 0s
local_file.file_do: Destroying... [id=4936512e550300ed01d468cd8b7b699d6c3e8507]
local_file.file_do: Destruction complete after 0s
random_id.token_do: Destroying... [id=b0oE8Q]
random_id.token_do: Destruction complete after 0s
random_id.token_do: Creating...
random_id.token_do: Provisioning with 'local-exec'...
random_id.token_do (local-exec): Executing: ["/bin/sh" "-c" "echo 'Sleeping 20 sec' && sleep 20"]
random_id.token_do (local-exec): Sleeping 20 sec
random_id.token_do: Still creating... [00m10s elapsed]
random_id.token_do: Still creating... [00m19s elapsed]
random_id.token_do: Creation complete after 19s [id=ux50Qg]
local_file.file_do: Creating...
local_file.file_do: Creation complete after 0s [id=50861283055ea87763742843ba16e86117e810df]
terraform_data.file_do: Creating...
terraform_data.file_do: Provisioning with 'local-exec'...
terraform_data.file_do (local-exec): Executing: ["/bin/sh" "-c" "(echo -n 'File content: ' && cat /home/user/file_do.txt) || echo 'File does not exists!'"]
terraform_data.file_do (local-exec): File content: ux50Qg
terraform_data.file_do: Creation complete after 0s [id=21d28f43-eb80-eb9b-dbb8-ca6f271a0fde]
Apply complete! Resources: 3 added, 0 changed, 3 destroyed.
When dependency is explicitly defined with depends_on = [local_file.file_do],
Terraform ensures that provisioning of local_file.file_do is completed before
terraform_data.file_do is created. Execution order is guaranteed, even if there
is no direct attribute dependency.
Key takeaway:
depends_on should be used only when implicit dependencies are insufficient.
Terraform's approach to lifecycle and dependency management is one of its most powerful features. By default, Terraform builds a dependency graph from configuration references, determines safe execution order, and applies changes in a predictable and efficient manner. For the majority of use cases, these defaults are sufficient and require no additional configuration.
Lifecycle and dependency controls exist to address specific operational needs, not to replace Terraform's core model. Resource lifecycle meta-arguments allow you to influence how individual resources are created, replaced, or destroyed, while dependency management features ensure that related resources are applied in the correct order. Used correctly, these tools make infrastructure changes safer and easier to implement.
At the same time, these features should be applied with care. Overusing explicit dependencies, masking drift unnecessarily, or relying on CLI overrides can reduce clarity and introduce fragile behavior. Terraform configurations are most maintainable when intent is expressed declaratively and consistently in code, rather than enforced manually at apply time.
Best Practices Summary
depends_on for cases Terraform
cannot infer.create_before_destroy, prevent_destroy, ignore_changes,
and replace_triggered_by only when default behavior introduces risk or operational
constraints.-target, -replace, and legacy taint are
best suited for recovery or troubleshooting, not routine workflows.Understanding how Terraform manages dependencies and resource lifecycle and when to override that behavior allows you to build infrastructure that is both flexible and resilient. By aligning configurations with Terraform's model and applying explicit controls only when necessary, you can safely manage change at scale while preserving clarity and confidence in every workflow step.
More Terraform Tutorials
Getting Started with Terraform
Understanding Terraform Variable Precedence
Terraform Value Types Tutorial
Terraform count Explained with Practical Examples
Terraform for_each Tutorial with Practical Examples
Exploring Terraform dynamic Blocks with GCP Examples
Working with External Data in Terraform
Terraform Modules FAQ