Free — no signup required

Resolving "State Hell": Circular Dependencies

3 min read

The Chicken-and-Egg Problem

Even with a clear hierarchy in mind, real-world infrastructure can create situations where two stacks appear to depend on each other simultaneously. This is called a circular dependency, and it produces a deployment deadlock.

Here is a concrete example:

  1. Stack A (Application): Needs the RDS endpoint from Stack B to configure its environment variables.
  2. Stack B (Database): Needs the application's security group ID from Stack A to configure its ingress rule — so only the app can connect to the database.

Stack A cannot be applied because it needs Stack B's output. Stack B cannot be applied because it needs Stack A's output. You are deadlocked. Neither stack can be the first to apply.

This situation arises because two concerns — what the app needs and who can access the database — have been bundled into the wrong stacks.

Solution 1: The Shared Stack (Preferred)

The root cause of the circular dependency is that the security group belongs to neither Stack A nor Stack B — it is a shared resource that both stacks depend on. The fix is to extract it into a lower-level stack.

A new, lower-level stack owns both security groups and the ingress rule between them, referencing only the network layer below it — nothing in either the application or database stack. Now the dependency graph is clean: security groups first, then database, then application. Apply order: security groups → database → application. No deadlock.

Solution 2: Decoupling with AWS SSM Parameter Store

Sometimes you cannot restructure the stacks — perhaps they are owned by different teams with different release cycles. In this case, use AWS SSM Parameter Store as a decoupling broker. Instead of Stack B reading Stack A's state directly via terraform_remote_state, Stack A writes its security group ID to an SSM parameter as a side effect of its own apply, and Stack B reads that same value back via a standard SSM data source — no state dependency between the two stacks at all, just a shared parameter name acting as the contract between them.

This requires a two-pass apply (apply Stack A first to write the SSM parameter, then apply Stack B), but it eliminates the hard state lock between the two stacks. Teams can now evolve their stacks independently as long as the SSM parameter contract is maintained.

Trade-off comparison:

Approach Best for Downside
Shared Stack Resources you control, same team Requires restructuring
SSM Parameter Store Cross-team dependencies Two-pass apply, contract must be documented
terraform_remote_state Same team, clear hierarchy Creates hard coupling between state files

Key Point: Circular dependencies are always a symptom of misplaced resource ownership. The fix is never a clever workaround — it is moving the shared resource to the correct layer. SSM Parameter Store is a pragmatic escape hatch for cross-team boundaries, but it trades hard coupling for a soft contract that must be maintained through documentation and convention.

This is one of 18 chapters

Every chapter is free — Kubernetes, Terraform, SRE, distributed systems, and more — with fast daily review built in.

Sign up free