Understanding and Resolving “Error: Cycle” in Terraform: Circular Dependency Explained


3 views

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:

  1. Run terraform plan -out=tfplan then terraform show -json tfplan to inspect dependencies
  2. Look for implicit dependencies created by references
  3. Check for indirect cycles through multiple resources
  4. Use count/for_each carefully as they can introduce hidden cycles