Nested & cross-stack references

Compose big infrastructure.

Intermediate12 min · lesson 7 of 12

One giant template becomes unmanageable, so CloudFormation offers two ways to compose. Nested stacks make a stack that contains other stacks: a parent template declares AWS::CloudFormation::Stack resources pointing at child templates in S3, passing parameters down and reading outputs back up. The whole tree is created, updated, and deleted as one, which is ideal for reusable building blocks (a standard VPC, a standard bucket) instantiated many times.

TWO WAYS TO COMPOSE STACKS
Nested stacks (one lifecycle)
Parent template
declares AWS::CloudFormation::Stack
Child stacks
reusable VPC / bucket templates in S3
Params down, outputs up
created & deleted as one tree
Cross-stack (separate lifecycles)
Producer exports Output
globally-unique export name
Consumer !ImportValue
reads the exported value
Independent deploys
different teams, own lifecycles
Nested = refactor freely as one unit; cross-stack export = a locked public contract between stacks.
parent.yaml
Resources:
Network:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: https://s3.../vpc.yaml
Parameters: { Cidr: 10.0.0.0/16 }
App:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: https://s3.../app.yaml
Parameters:
SubnetId: !GetAtt Network.Outputs.PrivateSubnetId # child output -> parent

Cross-stack references

The other pattern keeps stacks separate and independently deployed, wired by exports. A producer stack exports an Output with a globally-unique name; a consumer stack reads it with !ImportValue. This suits stacks owned by different teams with different lifecycles — a long-lived networking stack exporting subnet IDs that many app stacks import — where nested stacks’ all-as-one lifecycle would be too tightly coupled.

consumer.yaml
Resources:
Lb:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Subnets:
- !ImportValue prod-private-subnet-a
- !ImportValue prod-private-subnet-b
You cannot delete an exported value in use
CloudFormation refuses to delete or modify an export that another stack is importing — which protects consumers but can also block you: you cannot change a producer’s exported output until every importer stops using it. Choose exports deliberately (they are a public contract), and prefer nested stacks when you want to refactor freely without that cross-stack lock.