← All posts
CloudFormation

Using Troposphere to create CloudFormation stack template

If you’ve ever wrote AWS CloudFormation template, you probably know that it can be a daunting task. Luckily, it can be much easier, if you use Python’s library “Troposphere”.

Troposphere lets you create Python objects in place of CloudFormation elements, does some basic validation of your input and generates the JSON template for CloudFormation for you. It is much easier and cleaner to use that writing JSON templates manually.

To install troposphere, just run:

pip install troposphere

A very simple stack that creates a new VPC with private and public subnets in AZs A-C, can look like this:

  1from troposphere import Ref, Template, Tags, Join
  2from troposphere.ec2 import VPC, Subnet, NetworkAcl, NetworkAclEntry, InternetGateway, \
  3VPCGatewayAttachment, RouteTable, Route, SubnetRouteTableAssociation, SubnetNetworkAclAssociation
  4
  5VPC_NETWORK = "172.21.0.0/16"
  6VPC_PRIVATE_A = "172.21.1.0/24"
  7VPC_PRIVATE_B = "172.21.2.0/24"
  8VPC_PRIVATE_C = "172.21.3.0/24"
  9VPC_PUBLIC_A = "172.21.128.0/24"
 10VPC_PUBLIC_B = "172.21.129.0/24"
 11VPC_PUBLIC_C = "172.21.130.0/24"
 12
 13t = Template()
 14
 15t.add_description("Stack creating a VPC")
 16
 17vpc = t.add_resource(VPC(
 18  "VPC",
 19  CidrBlock=VPC_NETWORK,
 20  InstanceTenancy="default",
 21  EnableDnsSupport=True,
 22  EnableDnsHostnames=False,
 23  Tags=Tags(
 24    Name=Ref("AWS::StackName")
 25  )
 26))
 27
 28# internet gateway
 29internetGateway = t.add_resource(InternetGateway(
 30  "InternetGateway",
 31  Tags=Tags(
 32    Name=Join("", [Ref("AWS::StackName"), "-gateway"]),
 33  ),
 34))
 35
 36gatewayAttachment = t.add_resource(VPCGatewayAttachment(
 37  "InternetGatewayAttachment",
 38  InternetGatewayId=Ref(internetGateway),
 39  VpcId=Ref(vpc)
 40))
 41
 42# public routing table
 43publicRouteTable = t.add_resource(RouteTable(
 44  "PublicRouteTable",
 45  VpcId=Ref(vpc),
 46  Tags=Tags(
 47   Name=Join("-", [Ref("AWS::StackName"), "public-rt"]),
 48  ),
 49))
 50
 51privateRouteTable = t.add_resource(RouteTable(
 52  "PrivateRouteTable",
 53  VpcId=Ref(vpc),
 54  Tags=Tags(
 55    Name=Join("-", [Ref("AWS::StackName"), "private-rt"]),
 56  ),
 57))
 58
 59internetRoute = t.add_resource(Route(
 60  "RouteToInternet",
 61  DestinationCidrBlock="0.0.0.0/0",
 62  GatewayId=Ref(internetGateway),
 63  RouteTableId=Ref(publicRouteTable),
 64  DependsOn=gatewayAttachment.title
 65))
 66
 67# private subnetworks
 68subnetPrivateA = t.add_resource(Subnet(
 69  "StackPrivateSubnetA",
 70  AvailabilityZone=Join("", [Ref("AWS::Region"), "a"]),
 71  CidrBlock=VPC_PRIVATE_A,
 72  MapPublicIpOnLaunch=False,
 73  Tags=Tags(
 74    Name=Join("", [Ref("AWS::StackName"), " private subnet A"]),
 75  ),
 76  VpcId=Ref(vpc)
 77))
 78
 79t.add_resource(SubnetRouteTableAssociation(
 80  "PrivateSubnetARouteTable",
 81  RouteTableId=Ref(privateRouteTable),
 82  SubnetId=Ref(subnetPrivateA)
 83))
 84
 85subnetPrivateB = t.add_resource(Subnet(
 86  "StackPrivateSubnetB",
 87  AvailabilityZone=Join("", [Ref("AWS::Region"), "b"]),
 88  CidrBlock=VPC_PRIVATE_B,
 89  MapPublicIpOnLaunch=False,
 90  Tags=Tags(
 91    Name=Join("", [Ref("AWS::StackName"), " private subnet B"]),
 92  ),
 93  VpcId=Ref(vpc)
 94))
 95
 96t.add_resource(SubnetRouteTableAssociation(
 97  "PrivateSubnetBRouteTable",
 98  RouteTableId=Ref(privateRouteTable),
 99  SubnetId=Ref(subnetPrivateB)
100))
101
102subnetPrivateC = t.add_resource(Subnet(
103  "StackPrivateSubnetC",
104  AvailabilityZone=Join("", [Ref("AWS::Region"), "c"]),
105  CidrBlock=VPC_PRIVATE_C,
106  MapPublicIpOnLaunch=False,
107  Tags=Tags(
108    Name=Join("", [Ref("AWS::StackName"), " private subnet C"]),
109  ),
110  VpcId=Ref(vpc)
111))
112
113t.add_resource(SubnetRouteTableAssociation(
114  "PrivateSubnetCRouteTable",
115  RouteTableId=Ref(privateRouteTable),
116  SubnetId=Ref(subnetPrivateC)
117))
118
119# public subnetworks
120subnetPublicA = t.add_resource(Subnet(
121  "StackPublicSubnetA",
122  AvailabilityZone=Join("", [Ref("AWS::Region"), "a"]),
123  CidrBlock=VPC_PUBLIC_A,
124  MapPublicIpOnLaunch=True,
125  Tags=Tags(
126    Name=Join("", [Ref("AWS::StackName"), " public subnet A"]),
127  ),
128  VpcId=Ref(vpc),
129))
130
131t.add_resource(SubnetRouteTableAssociation(
132  "PublicSubnetARouteTable",
133  RouteTableId=Ref(publicRouteTable),
134  SubnetId=Ref(subnetPublicA)
135))
136
137subnetPublicB = t.add_resource(Subnet(
138  "StackPublicSubnetB",
139  AvailabilityZone=Join("", [Ref("AWS::Region"), "b"]),
140  CidrBlock=VPC_PUBLIC_B,
141  MapPublicIpOnLaunch=True,
142  Tags=Tags(
143    Name=Join("", [Ref("AWS::StackName"), " public subnet B"]),
144  ),
145  VpcId=Ref(vpc),
146))
147
148t.add_resource(SubnetRouteTableAssociation(
149  "PublicSubnetBRouteTable",
150  RouteTableId=Ref(publicRouteTable),
151  SubnetId=Ref(subnetPublicB)
152))
153
154subnetPublicC = t.add_resource(Subnet(
155  "StackPublicSubnetC",
156  AvailabilityZone=Join("", [Ref("AWS::Region"), "c"]),
157  CidrBlock=VPC_PUBLIC_C,
158  MapPublicIpOnLaunch=True,
159  Tags=Tags(
160    Name=Join("", [Ref("AWS::StackName"), " public subnet C"]),
161  ),
162  VpcId=Ref(vpc)
163))
164
165t.add_resource(SubnetRouteTableAssociation(
166  "PublicSubnetCRouteTable",
167  RouteTableId=Ref(publicRouteTable),
168  SubnetId=Ref(subnetPublicC)
169))
170
171# network ACL for private subnets
172privateNetworkAcl = t.add_resource(NetworkAcl(
173  "PrivateNetworkAcl",
174  VpcId=Ref(vpc),
175  Tags=Tags(
176    Name=Join("", [Ref("AWS::StackName"), "-private-nacl"]),
177  ),
178))
179
180t.add_resource(SubnetNetworkAclAssociation(
181  "PrivateNetworkAAclAss",
182  SubnetId=Ref(subnetPrivateA),
183  NetworkAclId=Ref(privateNetworkAcl)
184))
185
186t.add_resource(SubnetNetworkAclAssociation(
187  "PrivateNetworkBAclAss",
188  SubnetId=Ref(subnetPrivateB),
189  NetworkAclId=Ref(privateNetworkAcl)
190))
191
192t.add_resource(SubnetNetworkAclAssociation(
193  "PrivateNetworkCAclAss",
194  SubnetId=Ref(subnetPrivateC),
195  NetworkAclId=Ref(privateNetworkAcl)
196))
197
198t.add_resource(NetworkAclEntry(
199  "PrivateNetworkAclEntryIngress",
200  CidrBlock=VPC_NETWORK,
201  Egress=False,
202  NetworkAclId=Ref(privateNetworkAcl),
203  Protocol=-1,
204  RuleAction="allow",
205  RuleNumber=200
206))
207
208t.add_resource(NetworkAclEntry(
209  "PrivateNetworkAclEntryEgress",
210  CidrBlock=VPC_NETWORK,
211  Egress=True,
212  NetworkAclId=Ref(privateNetworkAcl),
213  Protocol=-1,
214  RuleAction="allow",
215  RuleNumber=200
216))
217
218print(t.to_json())

Save this as vpc_stack.py and run python vpc_stack.py. Output will be a JSON template that can be used for CloudFormation!

You can easily direct the output to a file, by running python vpc_stack.py > vpc_stack.json. The JSON file can be uploaded directly to CloudFormation via CLI or AWS Management Console.

To find out more and for full documentation, see troposphere on github: https://github.com/cloudtools/troposphere.

For more examples of CloudFormation stacks with troposphere, check out our github repo: https://github.com/MysteriousCode/cloudformation-examples.

Let's talk

Start with a free second opinion: 30 minutes with our founder. No account access needed, and you keep a short written read.

Schedule a meeting: our calendar