If you are a CloudFormation user you have probably come across a situation where an ID of a resource created in one stack (e.g. Subnet ID from VPC-Stack) is needed in another, independent stack, for example to create an EC2 instance in EC2-Stack.

The traditional approach is to pass Outputs from one template to Parameters in another template. However there are a few problems…

  1. The first problem with this method is that is that you somehow have to collect the Outputs from one stack and pass them to the Parameters in another. This can be done either manually or through some scripting like AWS-CLI or Ansible, but it’s an extra, mundane work.
  2. The second problem is that many stacks need the same set of parameters – VPC ID, Subnet IDs, perhaps some Security Group IDs, Route53 hosted zone name and ID, etc. The Parameters list can get very long and not very interesting.

There is a better option – CloudFormation Stack Exports

CloudFormation Stack Exports

The idea is that CFN Stacks that create resources Export their output values to a global namespace under some well known Name (global = account+region wide). Then the CFN Stacks that need those resources Import them from that global namespace using the known export Name.

For example in a VPC stack we can have an export like this:

Outputs:
  VpcId:
    Value: !Ref VPC
    Export:
      Name: MyVpcId

Then in the EC2 stack we can import it:

Resources:
  InstanceSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      [...]
      VpcId:
        Fn::ImportValue: MyVpcId

No stack parameters needed, all we need is to know that the VPC ID was exported under a name MyVpcId.

Namespaces—{Project}:{EnvName}:{EnvIndex}:{ResourceName}

The obvious problem with the above is that you may want to deploy multiple versions of your stacks – for example dev-1, dev-2, test-1, test-2, prod-1, prod-2, etc. They can’t all export MyVpcId because – as mentioned – the Exports namespace is global in the account/region and once one of your stacks exports MyVpcId no other stack can export the same name.

Solution? Namespaces!

They are not required or prescribed by AWS and it’s up to the user to come up with something sensible. Over time I settled on a simple form: {Project}:{EnvName}:{EnvIndex}:{ResourceName}

For example the ECS Cluster name in dev-1 environment of MyApp deployment will be exported as: myapp:dev:1:ecs-cluster

Sometimes you may want to share some resources between environments, e.g. you may want to have a single VPC for all your Dev environments. In that case I use all for the EnvIndex field, for example: myapp:dev:all:vpc-id

With this “namespace system” in place we only need to pass EnvName and EnvIndex Parameters to each template and the template will figure out all the rest.

# vpc-template.yml
---
Parameters:
  EnvName:
    Type: String

Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      [...]

Outputs:
  VpcId:
    Value: !Ref VPC
    Export:
      Name: !Sub "myapp:${EnvName}:all:vpc-id"

Then in the ECS stack’s Security Group we import it with Fn::ImportValue intrinsic function:

# ecs-template.yml
---
Parameters:
  EnvName:
    Type: String
  EnvIndex:
    Type: String

Resources:
  EcsSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      [...]
      VpcId:
        Fn::ImportValue: !Sub "myapp:${EnvName}:all:vpc-id"

And in the same stack we export the ECS Cluster Name and ARN, this time for a specific EnvIndex:

# still in ecs-template.yml
Outputs:
  EcsCluster:
    Value: !Ref EcsCluster
    Export:
      Name: !Sub "myapp:${EnvName}:${EnvIndex}:ecs-cluster-name"

  EcsClusterArn:
    Value: !GetAtt EcsCluster.Arn
    Export:
      Name: !Sub "myapp:${EnvName}:${EnvIndex}:ecs-cluster-arn"

And finally in the actual ECS Service stack that gets deployed through CI/CD we import the cluster name:

# task-template.yml
---
[...]
Resources:
  TaskService:
    Type: AWS::ECS::Service
     Properties:
      Cluster:
        Fn::ImportValue: !Sub "myapp:${EnvName}:${EnvIndex}:ecs-cluster-name"
      TaskDefinition: !Ref TaskDefinition
      [...]

This way we can build dev / test / prod stacks in the same account by setting EnvName stack parameter and also multiple versions of each (blue / green or 1, 2, 3, ..) by setting the EnvIndex parameter. And we don’t have to pass heaps of repetitive Parameters to each template.

View the exports

You can view all the exports in CloudFormation ➔ Exports tab:

And of course you can use AWS-CLI to look up the exports in your scripts with aws cloudformation list-exports

That’s all for today 🙂