Cross-stack coordination without Step Functions
Table of Contents
Almost every multi-stack AWS architecture has stacks that depend on other stacks. A network stack creates the VPC; an application stack needs the VPC ID. A shared-services stack owns the KMS key; a data stack needs the key ARN. A certificate stack provisions the ACM certificate; a load balancer stack needs the certificate ARN.
Managing these dependencies — deciding how values move between stacks and how deployment order is enforced — is a recurring design problem. Step Functions is often mentioned as the solution, and it handles complex conditional flows well. But for the common case of a simple dependency chain where stack B needs an output from stack A, Step Functions introduces operational overhead that may be disproportionate to the problem. The built-in CloudFormation patterns are worth understanding on their own terms before reaching for an orchestration service.
Cross-stack exports and imports
CloudFormation’s native mechanism for passing values between stacks is the
export and import system.
The producing stack declares an Output with an Export name.
The consuming stack references it with Fn::ImportValue.
| |
| |
The full syntax for outputs is covered in the CloudFormation outputs reference.
The constraint that matters most in practice is this: you cannot update or delete an export while it is referenced by another stack. If you want to rename an export, you must first update every consuming stack to remove the import, make the change in the producing stack, then update all consumers to use the new name. In a large estate where one network stack is imported by twenty application stacks, an export rename becomes a significant coordinated operation.
Cross-stack exports are a good fit when:
- The interface is stable and unlikely to be renamed.
- Both stacks are in the same AWS account and region.
- The same team manages both stacks.
They are poorly suited to cross-account architectures (exports are account and region-scoped), to values that change frequently, or to stacks managed by independent teams with different release cadences.
Nested stacks
Nested stacks use AWS::CloudFormation::Stack to embed one stack as a
resource inside another.
The parent template controls the child stack’s parameters, and child stack
outputs are available as resource attributes in the parent.
| |
This is the most tightly coupled pattern.
All stacks in the hierarchy update in a single CloudFormation operation,
which guarantees consistency: either everything updates together or
everything rolls back together.
The DependsOn attribute makes the deployment order explicit and enforced
by CloudFormation.
The nested stack pattern is documented in the using nested stacks guide.
The trade-off is the tight coupling itself. A change to any child template, or to any parameter in the parent, triggers an update of the entire parent stack. The parent template can grow large and become difficult to reason about. Individual stacks within the hierarchy cannot be updated in isolation.
Nested stacks work well when the parent and child stacks are owned by the same team, always deployed atomically, and do not need to evolve independently.
SSM as a loose-coupling mechanism
A common alternative is to have the producing stack write its output to an SSM parameter, and the consuming stack read it via a dynamic reference.
| |
| |
This breaks the hard CloudFormation dependency that cross-stack exports create. The consuming stack has no CloudFormation-enforced relationship with the producing stack. You can update the consuming stack without touching the producing stack. You can rename or refactor the producing stack without triggering a coordinated export rename across all consumers.
The SSM path itself becomes the interface contract. As long as the producing stack keeps the value at the agreed path, consumers can operate independently. This makes the SSM handoff pattern useful across team boundaries: one team publishes values to agreed paths, and another team reads them without owning or depending on the producing stack.
Deployment order remains your responsibility.
The SSM parameter must exist before the consuming stack references it.
If the producing stack has not been deployed yet, the {{resolve:ssm:...}}
reference resolves to nothing and the consuming stack fails.
Documenting the SSM path schema and which stack owns each path is
essential for this pattern to remain maintainable at scale.
Wait conditions
AWS::CloudFormation::WaitCondition and AWS::CloudFormation::WaitConditionHandle
allow a stack to pause until it receives an external signal before proceeding.
| |
An external process — a CI pipeline step, a Lambda function, or another stack’s completion hook — sends a success signal to the wait condition handle URL. CloudFormation unblocks and continues creating resources that depend on the wait condition.
Wait conditions are useful when the dependency is readiness rather than a value: stack B should not proceed until stack A’s resources are fully operational, not just created. They can also coordinate with non-CloudFormation processes that need to complete before stack resources are provisioned.
The trade-off is latency and operational complexity. The signal must come from somewhere, which means something external must be aware of the wait condition handle URL and responsible for sending the signal. Debugging a stuck wait condition requires identifying why the signal was never sent.
Choosing the right pattern
None of these patterns is universally correct. The choice follows from the relationship between the stacks and the teams that manage them.
Cross-stack exports suit stable interfaces between stacks in the same account, owned by the same team. Accept the constraint: renaming an export requires coordinated updates across all consumers.
Nested stacks suit tightly related resources that always deploy together and are owned by one team. Accept the constraint: individual stacks cannot be updated in isolation.
SSM handoff suits stacks that need to evolve independently, stacks across team boundaries, or architectures where the interface contract is better expressed as a documented SSM path schema than as a CloudFormation export name.
Wait conditions suit cases where readiness — not just a value — is the coordination point, or where a non-CloudFormation process must complete before stack resources are provisioned.
For simple sequential dependencies, cross-stack exports or SSM handoff cover most requirements. Step Functions adds value when the coordination logic is itself complex: parallel deployments, error-handling branches, retry policies, or human approval gates. For a straightforward “stack B needs stack A’s VPC ID,” the built-in patterns are sufficient and simpler to operate.