When Terraform throws an "Error: Cycle", it's indicating a circular dependency in your infrastructure configuration. This occurs when resources depend on each other in a way that creates an infinite loop, making it impossible for Terraform to determine the correct order of operations.
Here are three typical cases where you might encounter this error:
# Example 1: Mutual dependency between security group and instance
resource "aws_security_group" "web" {
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
security_groups = [aws_instance.web.id] # Creates cycle
}
}
resource "aws_instance" "web" {
vpc_security_group_ids = [aws_security_group.web.id]
}
To identify cycles in complex configurations:
terraform graph | dot -Tsvg > graph.svg
This generates a visual representation of your resource dependencies where cycles appear as loops in the graph.
Here's how to refactor the previous example:
# Fixed version using separate security group rules
resource "aws_security_group" "web" {
name = "web-sg"
}
resource "aws_security_group_rule" "ssh" {
type = "ingress"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
security_group_id = aws_security_group.web.id
}
resource "aws_instance" "web" {
vpc_security_group_ids = [aws_security_group.web.id]
}
For complex architectures:
- Use Terraform modules to create logical separation
- Implement dependency inversion patterns
- Consider using data sources instead of direct references
Some legitimate cases might appear cyclic but aren't:
# This isn't actually a cycle
resource "aws_iam_role" "example" {
assume_role_policy = data.aws_iam_policy_document.example.json
}
data "aws_iam_policy_document" "example" {
statement {
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["ec2.amazonaws.com"]
}
}
}
When Terraform throws an "Error: Cycle" message, it's essentially telling you that your infrastructure configuration contains circular dependencies - a situation where resources depend on each other in a way that creates an infinite loop. This is one of those frustrating errors that doesn't always have obvious documentation but is crucial for infrastructure-as-code practitioners to understand.
Let's examine some common scenarios where cycles occur:
# Example 1: Mutual dependency between security group and instance
resource "aws_security_group" "web" {
name = "web-sg"
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
security_groups = [aws_instance.web.id] # Creates cycle
}
}
resource "aws_instance" "web" {
ami = "ami-123456"
instance_type = "t2.micro"
vpc_security_group_ids = [aws_security_group.web.id]
}
Terraform builds a dependency graph during the planning phase. When it detects that Resource A depends on Resource B which in turn depends on Resource A (either directly or through a chain of dependencies), it aborts with the cycle error. The graph visualization makes this particularly clear if you run:
terraform graph | dot -Tsvg > graph.svg
Here are effective ways to resolve circular dependencies:
# Solution 1: Use separate security group rules
resource "aws_security_group" "web" {
name = "web-sg"
}
resource "aws_security_group_rule" "ssh" {
type = "ingress"
from_port = 22
to_port = 22
protocol = "tcp"
security_group_id = aws_security_group.web.id
source_security_group_id = aws_security_group.web.id # No cycle
}
resource "aws_instance" "web" {
ami = "ami-123456"
instance_type = "t2.micro"
vpc_security_group_ids = [aws_security_group.web.id]
}
For more complex scenarios, consider these approaches:
- Use
depends_on
to explicitly declare dependencies - Implement Terraform modules to isolate components
- Utilize
null_resource
with triggers as intermediaries - Leverage data sources to break direct dependencies
When you encounter a cycle:
- Run
terraform plan -out=tfplan
thenterraform show -json tfplan
to inspect dependencies - Look for implicit dependencies created by references
- Check for indirect cycles through multiple resources
- Use count/for_each carefully as they can introduce hidden cycles