CloudFormation stacks can become unwieldy beasts, but breaking them into smaller, manageable nested stacks isn’t just about tidiness; it’s about enabling true modularity and parallel deployment.
Let’s see this in action. Imagine you have a single, massive CloudFormation template that defines your entire VPC, including subnets, internet gateways, NAT gateways, route tables, security groups, and EC2 instances. Deploying this takes ages, and if one small part fails, the whole thing rolls back.
Now, let’s break it down. We’ll create a "Parent" stack that orchestrates several "Child" stacks.
Parent Stack (parent-stack.yaml):
AWSTemplateFormatVersion: '2010-09-09'
Description: Parent stack to orchestrate nested infrastructure stacks.
Resources:
VPCStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: s3://your-bucket-name/templates/vpc-stack.yaml
Parameters:
VPCCidrBlock: 10.0.0.0/16
EC2Stack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: s3://your-bucket-name/templates/ec2-stack.yaml
Parameters:
InstanceType: t3.micro
ImageId: ami-0abcdef1234567890 # Replace with a valid AMI ID
VPCStackName: !Ref VPCStack # Pass the name of the VPC stack
SubnetId: !GetAtt VPCStack.Outputs.PublicSubnetId # Get an output from the VPC stack
Outputs:
EC2InstanceId:
Description: The ID of the EC2 instance created by the EC2 stack.
Value: !GetAtt EC2Stack.Outputs.InstanceId
Child Stack 1: VPC (vpc-stack.yaml):
AWSTemplateFormatVersion: '2010-09-09'
Description: Defines the VPC, subnets, and associated networking resources.
Parameters:
VPCCidrBlock:
Type: String
Description: The CIDR block for the VPC.
Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VPCCidrBlock
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: MyNestedVPC
PublicSubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.1.0/24
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: MyPublicSubnet
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: MyVPCInternetGateway
AttachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref VPC
InternetGatewayId: !Ref InternetGateway
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: MyPublicRouteTable
PublicRoute:
Type: AWS::EC2::Route
Properties:
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
RouteTableId: !Ref PublicRouteTable
SubnetRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet
RouteTableId: !Ref PublicRouteTable
Outputs:
VPCId:
Description: The ID of the VPC.
Value: !Ref VPC
PublicSubnetId:
Description: The ID of the public subnet.
Value: !Ref PublicSubnet
Child Stack 2: EC2 (ec2-stack.yaml):
AWSTemplateFormatVersion: '2010-09-09'
Description: Defines an EC2 instance within a specified VPC and subnet.
Parameters:
InstanceType:
Type: String
Description: The EC2 instance type.
ImageId:
Type: String
Description: The AMI ID for the EC2 instance.
VPCStackName:
Type: String
Description: The name of the CloudFormation stack that created the VPC.
SubnetId:
Type: AWS::EC2::Subnet::Id
Description: The ID of the subnet to launch the instance into.
Resources:
EC2Instance:
Type: AWS::EC2::Instance
Properties:
InstanceType: !Ref InstanceType
ImageId: !Ref ImageId
SubnetId: !Ref SubnetId
Tags:
- Key: Name
Value: MyNestedInstance
Outputs:
InstanceId:
Description: The ID of the EC2 instance.
Value: !Ref EC2Instance
To deploy this, you’d upload vpc-stack.yaml and ec2-stack.yaml to an S3 bucket (e.g., s3://your-bucket-name/templates/). Then, you’d create the parent-stack.yaml and deploy it via the AWS Console or AWS CLI.
When you deploy the parent stack, CloudFormation reads the TemplateURL for each nested stack and provisions them. Crucially, it understands dependencies: if EC2Stack needs information from VPCStack (like SubnetId), it uses !GetAtt VPCStack.Outputs.PublicSubnetId to retrieve that output. The VPCStackName: !Ref VPCStack parameter is a bit of a workaround; !Ref on a AWS::CloudFormation::Stack resource returns the stack name, which we can then pass to a child stack if it needs to reference its parent’s name for tagging or other logic.
The real power here is how CloudFormation manages the lifecycle. If vpc-stack.yaml fails, only the VPC resources are rolled back. The ec2-stack.yaml won’t even attempt to deploy. If ec2-stack.yaml fails after vpc-stack.yaml succeeds, only the EC2 instance is rolled back, leaving your VPC intact. This drastically reduces the blast radius of failures and speeds up deployments because related, independent stacks can be provisioned in parallel.
You can also reuse these child stacks across multiple parent stacks, promoting a DRY (Don’t Repeat Yourself) approach to infrastructure. For example, you could have a database-stack.yaml that’s included in multiple application stacks, all referencing a single, shared network-stack.yaml.
The one thing most people don’t realize is that the TemplateURL can also point to templates stored locally on your machine if you’re using the AWS CLI with the --template-body parameter, but for production, S3 is the standard and recommended approach. This allows you to keep your templates versioned and accessible.
The next logical step is to explore how to handle parameter and output passing between more deeply nested stacks, creating a hierarchical structure for complex environments.