CloudFormation Exports are a way to share values between different CloudFormation stacks, but they’re fundamentally a shared state database that CloudFormation manages, not a direct stack-to-stack communication channel.
Let’s see this in action. Imagine you have a VPC stack that creates a VPC, a subnet, and a route table. You want to use the subnet ID from this VPC in another stack that deploys an EC2 instance.
Here’s the VPCStack.yaml:
Resources:
MyVPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
Tags:
- Key: Name
Value: MySharedVPC
MySubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref MyVPC
CidrBlock: 10.0.1.0/24
Tags:
- Key: Name
Value: MySharedSubnet
Outputs:
MySubnetId:
Description: The ID of the shared subnet
Value: !Ref MySubnet
Export:
Name: MySharedSubnetID
# You can optionally add a Condition here if you only want to export
# the value when a certain condition is met.
# Condition: ExportSubnetID
And here’s the EC2Stack.yaml that uses the exported value:
Parameters:
SubnetId:
Description: The ID of the subnet to launch the EC2 instance into
Type: AWS::EC2::String
Resources:
MyEC2Instance:
Type: AWS::EC2::Instance
Properties:
ImageId: ami-0abcdef1234567890 # Replace with a valid AMI ID for your region
InstanceType: t2.micro
SubnetId: !Ref SubnetId
Tags:
- Key: Name
Value: MyInstanceUsingSharedSubnet
Outputs:
InstanceId:
Description: The ID of the EC2 instance
Value: !Ref MyEC2Instance
When you deploy VPCStack.yaml, CloudFormation will create the VPC and subnet, and then make the subnet ID available under the name MySharedSubnetID in its export database.
When you deploy EC2Stack.yaml, you don’t hardcode the subnet ID. Instead, you provide it as a parameter. Crucially, when you create the EC2Stack, you’ll specify the SubnetId parameter value like this:
aws cloudformation create-stack \
--stack-name EC2Stack \
--template-body file://EC2Stack.yaml \
--parameters \
ParameterKey=SubnetId,UsePreviousValue=false,ParameterValue=$(aws cloudformation list-exports --query "Exports[?Name=='MySharedSubnetID'].Value" --output text)
Notice the $(aws cloudformation list-exports ...) part. This command queries the CloudFormation service for exports named MySharedSubnetID and retrieves their values. CloudFormation then injects that value into the SubnetId parameter of your EC2Stack.
This mechanism allows you to decouple your infrastructure. The VPC team can manage the VPC and its subnets, and the application team can deploy EC2 instances without knowing the exact subnet ID upfront, as long as it’s exported.
The core problem this solves is managing dependencies between independently deployable infrastructure components. Without exports, you’d either have to manually copy-paste IDs (error-prone) or bake them into your templates, tightly coupling deployments. Exports create a registry of shared infrastructure outputs.
Internally, CloudFormation maintains a global, account-wide registry of these exported values. When you list-exports, you’re querying this registry. When you use Fn::ImportValue, CloudFormation looks up the specified export name in this registry and resolves it to the corresponding value. If the export doesn’t exist, or if the stack that created it has been deleted, the Fn::ImportValue will fail, and your stack creation will halt.
The most surprising thing about CloudFormation Exports is that they are not truly "dynamic" in the sense that a value can change in the exporting stack and immediately propagate. If the exporting stack is updated to a different subnet ID, the export’s value in the registry updates. However, any existing stacks that import that value will not automatically update. They are pinned to the value that was current when they were last deployed or updated. To update an importing stack, you must explicitly re-trigger an update for that stack, which will then re-evaluate the Fn::ImportValue and pull the new exported value.
The other key constraint is that an exported name must be unique within your AWS account and region. If you try to export a value with a name that already exists, CloudFormation will reject the update to the exporting stack. This uniqueness constraint is crucial for preventing accidental overwrites of shared infrastructure.
The next concept you’ll likely encounter is dealing with circular dependencies between stacks, which Exports can sometimes create if not managed carefully.