Terraform detected a circular dependency in your infrastructure configuration where resources depend on each other directly or through a chain, preventing Terraform from determining creation order. Break the cycle by refactoring resource relationships or using intermediate resources.
A dependency cycle occurs when Terraform detects that Resource A depends on Resource B, but Resource B also depends on Resource A (directly or indirectly through other resources). This creates an impossible situation where Terraform cannot determine which resource to create first. The error prevents any infrastructure changes from being applied because the dependency graph has no valid starting point.
Read the error message carefully. Terraform usually shows which resource type is involved in the cycle:
Error: Cycle: aws_security_group.frontend -> aws_security_group.backend -> aws_security_group.frontendNote the resource types and logical names. Run terraform graph | grep -E "(cycle|frontend|backend)" to visualize relationships if available.
Open your Terraform configuration files and find where these resources reference each other. Look for:
- Direct attribute references: aws_security_group.backend.id
- Implicit depends_on (via references)
- Explicit depends_on arguments
Example problematic code:
resource "aws_security_group" "frontend" {
ingress {
from_port = 443
to_port = 443
security_groups = [aws_security_group.backend.id] # References backend
}
}
resource "aws_security_group" "backend" {
ingress {
from_port = 3306
to_port = 3306
security_groups = [aws_security_group.frontend.id] # References frontend - CYCLE!
}
}Instead of defining ingress rules inline within security group resources, use separate aws_security_group_rule resources. This lets Terraform create both groups first without dependencies:
resource "aws_security_group" "frontend" {
name = "frontend"
# Removed ingress block
}
resource "aws_security_group" "backend" {
name = "backend"
# Removed ingress block
}
# Now define rules separately - no cycle
resource "aws_security_group_rule" "frontend_to_backend" {
type = "ingress"
from_port = 3306
to_port = 3306
protocol = "tcp"
security_group_id = aws_security_group.backend.id
source_security_group_id = aws_security_group.frontend.id
}
resource "aws_security_group_rule" "backend_to_frontend" {
type = "ingress"
from_port = 443
to_port = 443
protocol = "tcp"
security_group_id = aws_security_group.frontend.id
source_security_group_id = aws_security_group.backend.id
}For external resources you don't manage in this configuration, use data sources instead of creating new resources:
# Bad - creates implicit dependency
resource "aws_security_group_rule" "example" {
security_group_id = aws_security_group.mine.id
source_security_group_id = aws_security_group.existing.id # May not exist yet
}
# Good - fetches existing resource without creating dependency
data "aws_security_group" "existing" {
name = "existing-group-name"
}
resource "aws_security_group_rule" "example" {
security_group_id = aws_security_group.mine.id
source_security_group_id = data.aws_security_group.existing.id # No cycle
}If the cycle is between modules, restructure them:
# Module A depends on B, Module B depends on A = CYCLE
# Solution: Extract common outputs to a separate module
module "networking" {
source = "./modules/networking"
# Both A and B depend on this
}
module "database" {
source = "./modules/database"
vpc_id = module.networking.vpc_id # Depends on networking
}
module "api" {
source = "./modules/api"
vpc_id = module.networking.vpc_id # Depends on networking
db_endpoint = module.database.endpoint # Depends on database
}Create local values that both resources can reference instead of referencing each other:
locals {
backend_port = 3306
frontend_port = 443
}
resource "aws_security_group" "frontend" {
ingress {
from_port = local.frontend_port
to_port = local.frontend_port
protocol = "tcp"
cidr_blocks = ["10.0.0.0/8"]
}
}
resource "aws_security_group" "backend" {
ingress {
from_port = local.backend_port
to_port = local.backend_port
protocol = "tcp"
cidr_blocks = ["10.0.0.0/8"]
}
}After refactoring, test the configuration:
terraform init
terraform planVerify that:
- No cycle errors appear
- All resources show creation/modification as expected
- Dependency graph makes sense (use terraform graph if needed)
If cycles remain, repeat steps 1-2 to identify additional cycles.
Advanced cycle detection patterns:
Implicit vs Explicit Dependencies: Most cycles come from implicit dependencies (attribute references). Explicit depends_on should only be used when Terraform cannot infer a dependency automatically. Never use depends_on to fix cycles—it will only mask the problem.
Multi-level cycles: Sometimes cycles span 3+ resources (A->B->C->A). Use terraform graph command to visualize: terraform graph | dot -Tsvg > graph.svg (requires Graphviz).
Provider configuration cycles: If a provider's configuration references resources it manages, this creates a bootstrap problem. Solution: Use separate provider blocks or move the resource outside the managed infrastructure.
Terraform Cloud/Enterprise: Cycles can also occur when variable dependencies are misconfigured in sentinel policies or run triggers.
Prevention: Design infrastructure with clear hierarchical dependencies. Use modules to organize resources by concern (networking, compute, data). Always review terraform plan output before applying.
Error: Error installing helm release: cannot re-use a name that is still in use
How to fix "release name in use" error in Terraform with Helm
Error: Error creating GKE Cluster: BadRequest
BadRequest error creating GKE cluster in Terraform
Error: External program failed to produce valid JSON
External program failed to produce valid JSON
Error: Unsupported argument in child module call
How to fix "Unsupported argument in child module call" in Terraform
Error: network is unreachable
How to fix "network is unreachable" in Terraform