In today’s blog post, we are going to cover terraform depends_on meta argument. Terraform internally manage dependency between resources to determine the order in which resources will get provisioned, updated, or destroyed. Terraform does this by creating a dependency graph between the resources. For example say you need to provision a server and it’s storage. Now unless the storage is provisioned and ready to use, you cannot start provisioning the server. Terraform needs to identify the order in which it has to deploy, update or destroy a resource and terraform automatically manages this dependency graph.
However, sometimes Terraform’s automatic dependency inference is not enough. There are subtle, non-obvious dependencies that, if not explicitly handled, can lead to failed deployments, race conditions, or unexpected behavior. This is where the depends_on
meta-argument comes into picture.
The Problem: Implicit Dependencies and Their Limitations
Terraform’s intelligence lies in its ability to build a dependency graph by analyzing your configuration. If resource A’s attribute is used as an input to resource B, Terraform automatically infers that resource A must be created or updated before resource B.
Consider this common scenario:
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
}
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
}
resource "aws_instance" "web_server" {
ami = "ami-0abcdef1234567890" # Replace with a valid AMI ID
instance_type = "t2.micro"
subnet_id = aws_subnet.public.id
}
Here, Terraform implicitly understands:
aws_vpc.main
must exist beforeaws_subnet.public
(because ofvpc_id = aws_vpc.main.id
).aws_subnet.public
must exist beforeaws_instance.web_server
(because ofsubnet_id = aws_subnet.public.id
).
This system works for direct attribute relationships. However, implicit dependencies fall short in several important scenarios:
- No Direct Attribute Linkage: A resource might logically depend on another being fully operational, but there is no direct attribute in its configuration that links to the dependency. For example, a database user needs a database instance to be ready to accept connections, not just created.
- External Actions or State Changes: Terraform might need to wait for an event or action that occurs outside of its direct management, or for an external system to reflect a change made by Terraform.
- Provider Limitations or Race Conditions: Occasionally, a cloud provider’s API or a Terraform provider’s implementation might have subtle timing issues or race conditions that even implicit dependencies do not reliably resolve.
These are the situations where depends_on
steps in to provide explicit control.
Introducing depends_on
The depends_on
meta-argument allows you to explicitly define dependencies between blocks in your Terraform configuration. It forces Terraform to ensure that the specified dependencies are fully provisioned and stable before the block where depends_on
is declared is processed.
Syntax:
depends_on
is defined within a resource
, data
, or module
block and takes a list of references to other blocks.
resource "resource_type" "resource_name" {
# ... configuration ...
depends_on = [
resource_type.another_resource_name,
data.data_type.data_name,
module.my_module,
]
}
The key characteristic is that depends_on
accepts a list, meaning you can specify multiple dependencies for a single block.
Where depends_on Can Be Used (with Examples)
Let us explore its application across the core Terraform block types:
1. Resources
This is the most common and widely recognized use case for depends_on
. It ensures a resource is created or updated only after other specified resources are fully provisioned.
Scenario: An S3 bucket policy needs to reference an IAM role. While the policy attachment might implicitly depend on the role’s ARN, you want to ensure the IAM role is not just created but also fully propagated before the policy is attempted.
resource "aws_iam_role" "my_application_role" {
name = "my-application-role-2025"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "lambda.amazonaws.com"
}
},
]
})
}
resource "aws_s3_bucket" "app_logs" {
bucket = "my-application-logs-bucket-2025-unique" # Must be globally unique
}
resource "aws_s3_bucket_policy" "restrict_access" {
bucket = aws_s3_bucket.app_logs.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "AllowLambdaAccess"
Effect = "Allow"
Principal = {
AWS = aws_iam_role.my_application_role.arn
}
Action = [
"s3:GetObject",
"s3:PutObject"
]
Resource = "${aws_s3_bucket.app_logs.arn}/*"
},
]
})
# Explicitly wait for the IAM role to be fully available
# even though its ARN is referenced. This mitigates propagation delays.
depends_on = [aws_iam_role.my_application_role, aws_s3_bucket.app_logs]
}
2. Data Sources
You can use depends_on
with data
blocks to ensure that the data source is refreshed or read after specific resources have been created or updated, or after other data sources have completed their operations. This is important when the external state being queried by the data source relies on a prior Terraform action.
Scenario: You have a null_resource
that executes a script to create an AWS Systems Manager (SSM) Parameter, and then you immediately want to read that parameter using a data
block. Implicit dependency might not be enough to guarantee the parameter is available right away.
resource "null_resource" "create_ssm_param" {
provisioner "local-exec" {
command = "aws ssm put-parameter --name '/my-app/db-connection-string' --value 'some-secret-value' --type 'SecureString' --overwrite"
}
# This trigger ensures the provisioner runs on every apply if the value changes
# (in a real scenario, you'd manage secrets more securely, e.g., using Secrets Manager)
triggers = {
always_run = timestamp()
}
}
data "aws_ssm_parameter" "db_connection" {
name = "/my-app/db-connection-string"
type = "SecureString"
# Explicitly depend on the null_resource completing its execution
# to ensure the SSM parameter is written before we attempt to read it.
depends_on = [null_resource.create_ssm_param]
}
output "db_param_value" {
value = data.aws_ssm_parameter.db_connection.value
sensitive = true
}
3. Modules
Applying depends_on
to a module
block allows you to enforce that an entire module (and all the resources it defines) is deployed after other specified resources or other modules are complete. This provides a high-level ordering for complex infrastructure stacks.
Scenario: You have a database
module that provisions a database cluster and an application
module that deploys your application servers. The application must only start deploying once the database is fully set up and ready to accept connections. While there might be implicit dependencies via database outputs, a depends_on
on the module can ensure the entire database stack is stable.
# modules/database/main.tf
# (defines aws_rds_cluster, aws_rds_cluster_instance, etc.)
# modules/database/outputs.tf
# (defines db_endpoint, db_port)
module "database" {
source = "./modules/database"
# ... other database specific variables ...
}
# modules/application/main.tf
# (defines aws_ec2_instance, aws_lb, etc.)
module "application" {
source = "./modules/application"
# ... application specific variables ...
# Pass database outputs as inputs to the application module
db_connection_string = "jdbc:mysql://${module.database.db_endpoint}:${module.database.db_port}/myapp"
# Explicitly depend on the entire database module
# to ensure its complete deployment before starting the application stack.
depends_on = [module.database]
}
Best Practices and Considerations
While powerful, depends_on
should be used with care:
- Prioritize Implicit Dependencies: Always strive to use implicit dependencies (via attribute references or data sources) whenever possible. They are the most natural and robust way for Terraform to manage your graph.
depends_on
is a fallback for when implicit methods fail. - Clarity and Readability: When you do use
depends_on
, ensure the reason is clear. Add comments to your code explaining why this explicit dependency is necessary. Overusing it can make your configurations hard to read and debug. - Impact on Parallelism: Terraform is highly efficient at parallelizing resource creation. Every
depends_on
declaration creates a sequential bottleneck, forcing Terraform to wait. Excessive use can significantly increase yourterraform apply
times. - Debugging
depends_on
Issues: If you find a resource still failing despitedepends_on
, verify that the depended-upon object truly reaches the desired stable state (e.g., database accepting connections) and not just a “created” state. You might need customnull_resource
andprovisioner
blocks with checks for complex external dependencies. for_each
vs.depends_on
on Collection: When managing collections of resources created withcount
orfor_each
, you typically refer to the entire collection (e.g.,aws_instance.web_servers
) independs_on
if you want all instances to complete. If you need a specific instance, refer to it by its index or key (e.g.,aws_instance.web_servers[0]
).
Conclusion
The depends_on
meta-argument is an important feature in a Terraform practitioner’s toolkit. It helps you to explicitly manage dependencies that Terraform cannot automatically detect, ensuring your infrastructure is provisioned in the precise order required, even when dealing with complex inter-service relationships or external system interactions.
By understanding its application across resource
, data
, and module
blocks, and by adhering to best practices like prioritizing implicit dependencies and being mindful of its impact on parallelism, you can build more resilient, predictable, and robust infrastructure deployments using Terraform.
Author

Experienced Cloud & DevOps Engineer with hands-on experience in AWS, GCP, Terraform, Ansible, ELK, Docker, Git, GitLab, Python, PowerShell, Shell, and theoretical knowledge on Azure, Kubernetes & Jenkins.
In my free time, I write blogs on ckdbtech.com