Terraform Resource Dependencies
When building infrastructure with Terraform, resources rarely exist in isolation. Most real-world infrastructure involves components that depend on each other. For example, you might need to create a virtual network before launching servers within it, or provision a database before configuring your application that connects to it.
In this guide, we'll explore how Terraform manages resource dependencies, ensuring that your infrastructure is created, updated, and destroyed in the correct order.
Introduction to Resource Dependencies
Terraform uses a dependency graph to determine the order in which resources should be created, modified, or destroyed. This ensures that dependent resources are only acted upon after their dependencies have been successfully provisioned.
There are two primary types of dependencies in Terraform:
- Implicit dependencies - Inferred automatically by Terraform through reference expressions
- Explicit dependencies - Manually defined using the
depends_on
meta-argument
Understanding these dependencies is crucial for creating reliable and predictable infrastructure deployments.
Implicit Dependencies
Implicit dependencies occur when one resource references attributes of another resource. Terraform automatically detects these references and creates dependencies in the execution plan.
How Implicit Dependencies Work
When you reference an attribute from one resource in another resource's configuration, Terraform automatically understands that the second resource depends on the first.
Let's look at a basic example:
# First, create a VPC
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
tags = {
Name = "main-vpc"
}
}
# Then, create a subnet within the VPC
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id # This creates an implicit dependency
cidr_block = "10.0.1.0/24"
tags = {
Name = "public-subnet"
}
}
In this example, aws_subnet.public
has an implicit dependency on aws_vpc.main
because it references aws_vpc.main.id
. Terraform will ensure that the VPC is created before attempting to create the subnet.
Visualizing the Dependency Graph
You can use the terraform graph
command to visualize these dependencies:
terraform graph | dot -Tsvg > dependency_graph.svg
This creates an SVG visualization of your infrastructure's dependency graph.
Here's a simplified example of what a dependency graph might look like:
Explicit Dependencies
Sometimes, resources have dependencies that aren't expressed through attribute references. In these cases, you can use the depends_on
meta-argument to explicitly define dependencies.
Using the depends_on Meta-Argument
The depends_on
meta-argument takes a list of resources that should be created before the resource where it's defined.
resource "aws_s3_bucket" "example" {
bucket = "my-example-bucket"
}
resource "aws_s3_bucket_policy" "example" {
bucket = aws_s3_bucket.example.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = "*"
Action = "s3:GetObject"
Resource = "${aws_s3_bucket.example.arn}/*"
}
]
})
}
resource "aws_s3_bucket_notification" "bucket_notification" {
bucket = aws_s3_bucket.example.id
lambda_function {
lambda_function_arn = aws_lambda_function.func.arn
events = ["s3:ObjectCreated:*"]
}
depends_on = [aws_s3_bucket_policy.example]
}
In this example, we've explicitly declared that aws_s3_bucket_notification.bucket_notification
depends on aws_s3_bucket_policy.example
, even though there's no direct attribute reference between them. This ensures the bucket policy is applied before the notification configuration.
When to Use Explicit Dependencies
You should use explicit dependencies in the following scenarios:
- When resources have dependencies that aren't expressed through attribute references
- When you need to enforce a specific creation order for resources
- When dealing with side effects or eventual consistency issues
Complex Dependency Scenarios
Module Dependencies
Dependencies work not just with resources but also between modules:
module "vpc" {
source = "./modules/vpc"
# module configuration...
}
module "web_servers" {
source = "./modules/web_servers"
vpc_id = module.vpc.vpc_id
# other configuration...
depends_on = [module.vpc] # Explicit module dependency
}
In this example, the web_servers
module depends on the vpc
module, both implicitly (through the vpc_id
reference) and explicitly (through depends_on
).
Handling Dependency Cycles
Sometimes, you might accidentally create circular dependencies. Terraform will detect these and display an error:
Error: Cycle: aws_instance.a, aws_instance.b
To resolve dependency cycles:
- Refactor your configuration to break the cycle
- Use intermediate resources
- Consider if your design could be improved to avoid circular dependencies
Data Source Dependencies
Data sources also participate in the dependency graph:
data "aws_ami" "ubuntu" {
most_recent = true
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
owners = ["099720109477"] # Canonical
}
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
tags = {
Name = "WebServer"
}
}
In this example, aws_instance.web
depends on the aws_ami.ubuntu
data source.
Real-World Application: Web Application Infrastructure
Let's build a complete example of a web application infrastructure with proper dependencies:
# Network infrastructure
resource "aws_vpc" "app" {
cidr_block = "10.0.0.0/16"
tags = {
Name = "application-vpc"
}
}
resource "aws_subnet" "public" {
vpc_id = aws_vpc.app.id
cidr_block = "10.0.1.0/24"
availability_zone = "us-west-2a"
tags = {
Name = "public-subnet"
}
}
resource "aws_subnet" "private" {
vpc_id = aws_vpc.app.id
cidr_block = "10.0.2.0/24"
availability_zone = "us-west-2b"
tags = {
Name = "private-subnet"
}
}
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.app.id
tags = {
Name = "main-igw"
}
}
# Database tier
resource "aws_db_subnet_group" "main" {
name = "main-db-subnet-group"
subnet_ids = [aws_subnet.private.id, aws_subnet.public.id]
tags = {
Name = "Database subnet group"
}
}
resource "aws_security_group" "db" {
name = "database-sg"
description = "Allow MySQL traffic"
vpc_id = aws_vpc.app.id
ingress {
from_port = 3306
to_port = 3306
protocol = "tcp"
cidr_blocks = [aws_subnet.private.cidr_block]
}
}
resource "aws_db_instance" "main" {
allocated_storage = 10
engine = "mysql"
engine_version = "8.0"
instance_class = "db.t3.micro"
identifier = "app-database"
db_name = "appdb"
username = "admin"
password = "Change_me_in_production!" # Use variables in practice
parameter_group_name = "default.mysql8.0"
skip_final_snapshot = true
db_subnet_group_name = aws_db_subnet_group.main.name
vpc_security_group_ids = [aws_security_group.db.id]
depends_on = [aws_db_subnet_group.main]
}
# Web server tier
resource "aws_security_group" "web" {
name = "web-sg"
description = "Allow web traffic"
vpc_id = aws_vpc.app.id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0" # Sample AMI ID
instance_type = "t3.micro"
subnet_id = aws_subnet.public.id
vpc_security_group_ids = [aws_security_group.web.id]
user_data = <<-EOF
#!/bin/bash
echo "DB_HOST=${aws_db_instance.main.address}" > /etc/environment
echo "DB_PORT=${aws_db_instance.main.port}" >> /etc/environment
echo "DB_NAME=${aws_db_instance.main.db_name}" >> /etc/environment
echo "DB_USER=${aws_db_instance.main.username}" >> /etc/environment
# Start application
systemctl start my-web-app
EOF
depends_on = [aws_db_instance.main, aws_internet_gateway.main]
tags = {
Name = "WebServer"
}
}
In this example, note the dependencies:
- The database instance depends on the subnet group and security group
- The web server instance depends on the database instance and internet gateway
- Both tiers depend on the VPC and appropriate subnets
Best Practices for Managing Dependencies
- Leverage implicit dependencies whenever possible for cleaner code
- Use
depends_on
sparingly and only when necessary - Keep your dependency graph shallow by avoiding long chains of dependencies
- Use modules to encapsulate related resources and manage dependencies at a higher level
- Regularly visualize your dependency graph with
terraform graph
to understand the complexity - Document non-obvious dependencies with comments
Troubleshooting Dependency Issues
Common Problems and Solutions
-
Resource not found errors: This often indicates that you're referencing a resource before it's created. Check your dependencies.
Error: Error creating instance: InvalidVpcID.NotFound: The vpc ID 'vpc-12345' does not exist
Solution: Ensure proper dependencies are in place.
-
Dependency cycles: Restructure your resources to avoid circular references.
-
Eventual consistency issues: Some cloud resources take time to propagate. You might need explicit dependencies or retry logic.
The terraform plan Command
Always run terraform plan
before applying changes to understand how dependencies affect the execution order:
terraform plan
The plan output will show the creation, update, and destruction order based on your dependency graph.
Summary
Terraform resource dependencies are a fundamental concept that ensures your infrastructure is created, updated, and destroyed in the correct order. Understanding both implicit and explicit dependencies allows you to craft reliable infrastructure as code that behaves predictably.
Key points to remember:
- Implicit dependencies are automatically detected through reference expressions
- Explicit dependencies are manually defined with the
depends_on
meta-argument - The dependency graph determines the execution order of Terraform operations
- Well-managed dependencies lead to reliable, reproducible infrastructure deployments
Additional Resources and Exercises
Exercises
-
Basic Dependencies: Create a Terraform configuration with a VPC, subnet, and EC2 instance. Observe the implicit dependencies.
-
Explicit Dependencies: Extend the configuration by adding an S3 bucket and a custom policy that requires an explicit dependency.
-
Module Dependencies: Refactor your configuration to use modules and manage dependencies between them.
Additional Resources
- Terraform Resource Dependencies Documentation
- Terraform Graph Command Documentation
- Managing Complex Terraform Configurations
By understanding and correctly implementing resource dependencies in Terraform, you'll build more robust infrastructure that deploys reliably and predictably every time.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)