Infrastructure as code (IaC) has emerged as the golden standard for managing cloud infrastructure and components effectively, consistently, and according to best practices. AWS CloudFormation is a powerful IaC service that allows cloud engineers and developers to define and manage their AWS infrastructure as code.
In this article, we will explore AWS CloudFormation templates, the blueprints that describe AWS resources and their properties, how to create and use them, and best practices.
What we will cover:
CloudFormation is an IaC AWS-native service that helps you model and configure your resources declaratively. Using CloudFormation, you can manage and operate your AWS infrastructure efficiently, so you can spend less time managing infrastructure.
AWS CloudFormation handles provisioning, configuration, and automatic rollbacks. And it has features that help you manage resources at scale, such as templates and stacks.
Learn more: What is AWS CloudFormation? Key Concepts & Tutorial
Templates are a core component of CloudFormation used to define the desired state of your infrastructure declaratively. They are text files written in JSON or YAML format that describe a set of AWS resources and their configurations, serving as blueprints for creating and managing AWS resources.
Templates should be version-controlled in a repository and go through CI/CD pipeline flows for infrastructure changes.
What is the difference between the AWS CloudFormation template and the stack?
With CloudFormation, you can create templates that describe all the AWS resources needed for your environments, enabling you to create, update, and delete them in a controlled and predictable manner. Templates can be parameterized, allowing you to reuse the same template across different environments (dev, staging, production) by changing input parameters.
A stack is a collection of AWS resources that can be operated as a single unit. CloudFormation automatically manages the order of resource creation, updates, and deletion based on dependencies defined in your template. This ensures that resources are created in the correct order and prevents conflicts.
Before applying changes to a stack, CloudFormation allows you to preview the changes using Change Sets. This feature helps you understand the impact of your changes before actually implementing them.
If errors occur during stack creation or update, CloudFormation can automatically roll back to the last known stable state, helping maintain the integrity of your infrastructure.
To recap, the CloudFormation template is a JSON or YAML file that defines AWS resources and configurations. It’s the blueprint for the infrastructure. A stack is the actual set of AWS resources created and managed based on the template.
When you are instantiating a template, you basically create a stack. The template defines what resources should exist and how they should be configured, whereas the stack represents the actual implementation of those resources in your AWS account.
CloudFormation templates are composed of one or more sections, including:
- Format version
- Resources
- Description
- Metadata
- Parameters
- Mappings
- Conditions
- Rules
- Transform
- Outputs
The resources section is always required in every CloudFormation template you define. It includes the stack resources and their configuration properties and is considered the main component of the template. Each resource is defined with a unique logical name (or ID), type, and specific configuration details.
For example:
Resources:
LogicalResourceName:
Type: AWS::ProductIdentifier::ResourceType
Properties:
PropertyName: PropertyValue
Each resource must have a Type
attribute to identify it.
For example, an Amazon S3 bucket has AWS::S3::Bucket
as its resource type. You can use the Properties
attribute to define additional configuration options for each specific resource type.
For details on the properties supported for each resource type, see the topics in AWS resource and property types reference. Property values can be literal strings, lists of strings, Booleans, dynamic references, parameter references, pseudo references, or the value returned by a function.
Here’s an example of defining an S3 Bucket resource along with its properties:
Resources:
MyS3Bucket:
Type: 'AWS::S3::Bucket'
Properties:
PublicAccessBlockConfiguration:
BlockPublicAcls: false
BlockPublicPolicy: false
IgnorePublicAcls: false
RestrictPublicBuckets: false
WebsiteConfiguration:
IndexDocument: index.html
ErrorDocument: error.html
Sometimes, you might need to reference or define a resource based on previously defined resources. CloudFormation provides several built-in functions that let you create dependencies between them and pass values from one to another. Two of the most common are the
and the Ref
GettAtt
functions.
The Ref function is commonly used to retrieve an identifying property of resources defined within the same CloudFormation template.
Here’s an example of referencing a Security Group for our EC2 instance configuration.
Resources:
Ec2Instance:
Type: 'AWS::EC2::Instance'
Properties:
SecurityGroupIds:
- !Ref InstanceSecurityGroup
KeyName: MyKey
InstanceSecurityGroup:
Type: 'AWS::EC2::SecurityGroup'
Properties:
GroupDescription: Enable SSH access via port 22 via anywhere
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: 0.0.0.0/0
To get a specific attribute of a resource, use the GettAtt function. For a detailed reference of all the available functions, see the topics in the Intrinsic function reference.
Here’s an example of using this function to get the S3 bucket’s domain name attribute:
Parameters in AWS Cloudformation templates allow you to customize your templates by providing a mechanism to input custom values. Effectively using parameters is the main method of making your templates reusable across different use cases, scenarios, and environments.
The only required attribute of a parameter is the Type
, which can be String
, Number
, or a CloudFormation-specific parameter type. As a best practice, you can add a Description
attribute and a default value where applicable. For a full list, check out Parameter Properties.
Here’s an example of defining a parameter for an EC2 instance type:
Parameters:
InstanceType:
Description: The instance type of the EC2 instance. Allowed values are t3.micro, t3.small, t3.medium, or t3.large. Default is t3.micro.
Type: String
Default: t3.micro
AllowedValues:
- t3.micro
- t3.small
- t3.medium
- t3.large
Another useful type of parameter is pseudo parameters. Pseudo parameters are predefined by AWS CloudFormation for you and are available to use in your templates without declaring them. Common examples of pseudo parameters are:
AWS::AccountId
AWS::Region
AWS::StackName
CloudFormation template mappings can help create key-value pairs based on dependencies or conditions. A common use case for mappings is to define a different set of values depending on the AWS region where the stack is deployed.
Here’s an example of defining a different AMI for EC2 instances based on the region, as AMIs are region-specific resources:
Mappings:
RegionMap:
us-east-2:
AmiId: ami-123abc
us-west-2:
AmiId: ami-456def
eu-west-2:
AmiId: ami-789ghi
To retrieve values in a map, you can use the FindInMap
intrinsic function within the Resources section of your template. Here’s an example of fetching the respective value from our RegionMap
based on the actual value of AWS::Region
pseudo parameter.
Resources:
myEC2:
Type: "AWS::EC2::Instance"
Properties:
ImageId: !FindInMap [RegionMap, !Ref "AWS::Region", AmiId]
InstanceType: t3.small
With the Outputs
section of templates, you can declare output values for your stack. These values can be passed to other stacks to cross-reference created resources or capture information you can pass to other systems.
Here’s an example of declaring a few outputs:
Outputs:
InstanceID:
Description: The EC2 Instance ID
Value: !Ref MyEC2Instance
VPCID:
Description: The VPC ID
Value: !Ref MyVPC
You can leverage the Metadata
section to include further details about your template in JSON or YAML. For example, you can add further descriptions or implementation details about specific components of your templates.
Metadata:
Networking:
Description: "Information about the system’s networking"
With CloudFormation templates Conditions
, you can define statements to control entity creation and configuration based on something. For example, with Conditions
, you can specify a resource creation only if a condition results in true
. Similarly, you can define a resource conditionally properly.
A use case for conditions is to reuse templates with different contexts, such as test, staging, dev, and production environments. Based on an input parameter that defines the environment type, you can create the resources and respective configurations for the specific environment.
Before creating or updating any resources, conditions are evaluated during stack creation or update.
Here’s an example based on the environment type:
AWSTemplateFormatVersion: 2010-09-09
Parameters:
EnvironmentType:
Description: Environment type.
Default: test
Type: String
AllowedValues:
- prod
- preprod
- staging
- dev
- test
ConstraintDescription: must specify prod, preprod, staging, dev, or test.
Conditions:
CreateProdResources: !Equals
- !Ref EnvironmentType
- prod
Resources:
EC2Instance:
Type: 'AWS::EC2::Instance'
Properties:
ImageId: ami-08d8ac128e0a1b91c
InstanceType: !If [ CreateProdResources, c5.xlarge, t3.small]
The optional Transform
section specifies one or more macros that CloudFormation uses to process your template in some way. Macros can perform simple tasks like finding and replacing text or make more extensive transformations to the entire template.
Rules
allow you to validate a combination of parameters during stack creation or update before actually creating or updating resources.
Let’s check an example that validates the value of the InstanceType
parameter for an EC2 instance depending on the environment type:
Rules:
stagingInstanceType:
RuleCondition: !Equals
- !Ref EnvironmentType
- staging
Assertions:
- Assert:
'Fn::Contains':
- - t3.small
- !Ref InstanceType
AssertDescription: 'For a staging environment, the instance type must be t3.small'
productionInstanceType:
RuleCondition: !Equals
- !Ref EnvironmentType
- production
Assertions:
- Assert:
'Fn::Contains':
- - c5.xlarge
- !Ref InstanceType
AssertDescription: 'For a production environment, the instance type must be c5.xlarge'
In this example, we define two rules to check for the environment type and allow only the type t3.small
for staging environments and c5.xlarge
for production environments.
If we try to deploy a template that breaks these rules, we will get an error:
The Description
section of the CloudFormation templates allows for a textual description of the template and its resources.
The optional AWSTemplateFormatVersion
section identifies the current template format version. The latest template format version is 2010-09-09
.
Leveraging CloudFormation templates to provision and manage AWS infrastructure can offer numerous advantages. Here are some benefits of CloudFormation adoption and template usage:
Version control and auditability
By defining your infrastructure as CloudFormation templates, you can store their definitions in Git, allowing you to track changes and collaborate effectively with other team members. All changes to environments pass through Git, maintaining a complete history.
Automated deployments
CloudFormation enables automated infrastructure deployments, offering a consistent and repeatable approach to change management. This simplifies tasks like performing upgrades, rolling back changes, and auditing modifications. By automating these processes, your team can work more efficiently, rapidly provisioning resources using best practices and reusable templates for common patterns.
Security, compliance, and disaster recovery
By adopting CloudFormation, you can enforce security best practices and compliance requirements baked into your reusable templates. This ensures that deployments, operations, and configurations comply with organizations’ policies and standards, reducing the risk of misconfigurations and vulnerabilities.
Furthermore, because the desired state of all our environments is defined in CloudFormation templates, we can recreate our environments from scratch, making disaster recovery scenarios much more manageable.
Deep integration with other AWS services
CloudFormation integrates with different AWS services, such as AWS Organizations, which allows you to manage complex multi-account and multi-region deployments.
Now that we understand the basics and benefits of CloudFormation templates let’s examine an end-to-end example of creating, validating, and deploying one.
How to create a CloudFormation template
To create a CloudFormation template, use the sections explained earlier and write a YAML manifest with all the resources you want to create.
Here’s a complete example that creates a VPC, a public subnet, a security group, and an EC2 instance:
example_template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: 'AWS CloudFormation template to create a VPC, EC2 instance, and Security Group'
Parameters:
EnvironmentName:
Description: An environment name that is prefixed to resource names
Type: String
Default: Dev
VpcCIDR:
Description: Please enter the IP range (CIDR notation) for this VPC
Type: String
Default: 10.0.0.0/16
PublicSubnetCIDR:
Description: Please enter the IP range (CIDR notation) for the public subnet
Type: String
Default: 10.0.1.0/24
InstanceType:
Description: EC2 instance type
Type: String
Default: t2.micro
AllowedValues:
- t2.micro
- t2.small
- t2.medium
ConstraintDescription: Must be a valid EC2 instance type.
LatestAmiId:
Type: 'AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>'
Default: '/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2'
Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VpcCIDR
EnableDnsHostnames: true
EnableDnsSupport: true
InstanceTenancy: default
Tags:
- Key: Name
Value: !Sub ${EnvironmentName} VPC
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Sub ${EnvironmentName} IGW
InternetGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
InternetGatewayId: !Ref InternetGateway
VpcId: !Ref VPC
PublicSubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select [ 0, !GetAZs '' ]
CidrBlock: !Ref PublicSubnetCIDR
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub ${EnvironmentName} Public Subnet
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${EnvironmentName} Public Routes
DefaultPublicRoute:
Type: AWS::EC2::Route
DependsOn: InternetGatewayAttachment
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
PublicSubnetRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PublicRouteTable
SubnetId: !Ref PublicSubnet
EC2SecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: "Allow HTTP and SSH"
GroupDescription: "Allow HTTP and SSH inbound and all outbound traffic"
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: 0.0.0.0/0
SecurityGroupEgress:
- IpProtocol: -1
FromPort: -1
ToPort: -1
CidrIp: 0.0.0.0/0
VpcId: !Ref VPC
EC2Instance:
Type: AWS::EC2::Instance
Properties:
InstanceType: !Ref InstanceType
SecurityGroupIds:
- !Ref EC2SecurityGroup
SubnetId: !Ref PublicSubnet
ImageId: !Ref LatestAmiId
Tags:
- Key: Name
Value: !Sub ${EnvironmentName} EC2 Instance
Outputs:
VPC:
Description: A reference to the created VPC
Value: !Ref VPC
Export:
Name: !Sub ${EnvironmentName}-VPCID
PublicSubnet:
Description: A reference to the public subnet
Value: !Ref PublicSubnet
Export:
Name: !Sub ${EnvironmentName}-PUBLIC-SUBNET
EC2InstancePublicDNS:
Description: Public DNS of EC2 instance
Value: !GetAtt EC2Instance.PublicDnsName
Let’s break down the template and the different sections we used:
- As a first step, we defined the
Template Version
andDescription
. - Next, we define different parameters such as
EnvironmentName
andInstanceType
, among others. - Right after, we define our actual resources along with their configuration, including VPC, EC2, Public Subnet, Internet Gateway, and Security Group.
- Finally, we define a few template outputs to be exported and possibly used or references in other stacks or for easy access.
This template creates basic networking components such as a VPC with a public subnet and an EC2 instance accessible via SSH and HTTP.
Apart from manually authoring CloudFormation templates, you can also use several tools to fast-track your development:
- You can leverage AWS Application Composer, a visual interface for designing templates. Application Composer allows you to compile your templates by dragging and dropping resources to a canvas to build your application visually.
- For existing resources created manually that CloudFormation doesn’t manage, you can use the IaC Generator tool to generate the templates and bring entire applications under CloudFormation management.
- Another popular way to define infrastructure as code is using the AWS Cloud Development Kit (CDK) and programming languages such as Python or Java. CDK synthesizes CloudFormation templates from your code, and many users prefer this approach due to the advanced code reuse and abstractions it offers.
How to validate a CloudFormation template
To validate CloudFormation templates syntactically, utilize the ‘validate-template’ CLI command in your automation flows. If you deploy templates via the console, this validation is performed automatically.
For more complex validations, as well as basic syntax checks, you can use additional tools in your CI/CD pipelines, such as CloudFormation Linter (cfn-lint) and CloudFormation Rain (rain fmt). Another way to validate your templates is to use an IDE extension to provide real-time validation during authoring.
How to deploy a CloudFormation template
There are several ways to deploy CloudFormation templates for different scenarios and preferences. The most straightforward way is to use the AWS Management Console, navigate to the CloudFormation service, and create a stack to deploy the resources based on a template.
A standard method to deploy CloudFormation templates is to integrate deployments into CI/CD pipelines. For example, you can define AWS CodePipeline flows to automate deployments based on source control changes.
Another way is to use the AWS CLI either manually, via scripts, or in an automated flow (e.g., as part of a CI/CD pipeline). For such cases, check out the aws cloud formation create-stack
command.
Let’s take the template example we created above and deploy it via the AWS Management Console and the CloudFormation Service.
Select the `Create stack` option:
On the next screen, select `Choose an existing template` and `Upload a template file`. Then, select `Choose file` to upload the YAML file we created previously and hit `Next.`
Enter a stack name and optionally modify any of the input parameters. You should already see the default values we configured in the template. Click `Next` to continue.
On the next page, you can add Tags, use a specific IAM role while deploying, and define stack failure options. Possible options include rolling back stack resources or preserving successfully provisioned resources. Other options you can configure include adding a stack policy, rollback configuration, and notification options.
For this example, we will use the defaults. Click `Next` to continue.
On the `Review and create` page, you can review your configuration options, parameter values, and other options. To proceed with deployment, click `Submit` at the bottom of the page.
Your stack moves in `CREATE_IN_PROGRESS` state:
On the `Events` tab, you can see the actual events of the deployment as they happen:
After the successful deployment of our stack, select the `Resources` tab to get an overview of all the created resources:
Finally, check out the `Outputs` tab to check all the exported values from the newly created resources:
Here are some best practices for creating and using CloudFormation templates.
- Store your CloudFormation templates in Git repositories or S3.
- Use YAML for better readability. While CloudFormation supports JSON, YAML is typically a more human-readable format that supports comments and is less error-prone to syntax errors.
- Make your templates reusable and dynamic using parameters, intrinsic functions, and conditionals.
- Implement an elaborate naming strategy. Use logical names for your resources that clearly state their purpose.
- Because your resources will depend on each other, leverage the
DependsOn
attribute to ensure resources are created in the correct order. - For complex architectures and environments, explore advanced features and functionalities such as nested stacks and StackSets.
- Use linting and validation tools such as
cfn-lint
.
Spacelift is an infrastructure orchestration platform that increases your infrastructure deployment speed without sacrificing control. With Spacelift, you can provision, configure, and govern with one or more automated workflows that orchestrate Terraform, OpenTofu, Terragrunt, Pulumi, CloudFormation, Ansible, and Kubernetes.
You don’t need to define all the prerequisite steps for installing and configuring the infrastructure tool you are using, nor the deployment and security steps, as they are all available in the default workflow.
Spacelift can be configured with AWS CloudFormation as a backend. See the image below:
When you select the CloudFormation backend, you need to provide a little information — the AWS Region where the stack should be created, the stack name, the template file name, and the S3 bucket where the template will be uploaded and then executed.
All runs of the CloudFormation stack are completed with change sets.
Let’s take a look at the successful execution log in Spacelift:
You can see the Unconfirmed status on this image. We used this manual step here to show the moment when some review of the change set can be made.
If you want to learn more about what you can do with Spacelift, check out this article.
This blog post delved into CloudFormation templates for defining and configuring IaC manifests. We looked into the benefits of using templates to create and manage multiple AWS resources and provided a detailed explanation of their key sections.
Next, we reviewed a complete example of authoring, validating, and deploying a template and discussed alternatives. Finally, we listed several best practices to consider when authoring your templates.
Would you like to improve IaC management in your organization? Book a demo with our engineering team to discuss your options in more detail.
Solve your infrastructure challenges
Spacelift is a flexible orchestration solution for IaC development. It delivers enhanced collaboration, automation, and controls to simplify and accelerate the provisioning of cloud-based infrastructures.