[November 20 Webinar] Detecting & Correcting Infrastructure Drift

➡️ Register Now

AWS

What is an AWS CloudFormation Template? [Examples]

cloudformation templates

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:

  1. What is AWS CloudFormation?
  2. What are AWS CloudFormation templates?
  3. Key CloudFormation parameters
  4. Benefits of using CloudFormation templates
  5. AWS CloudFormation template example
  6. CloudFormation templates best practices

What is AWS CloudFormation?

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

What are the CloudFormation templates?

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?

 

cloudformation template vs 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.

Key CloudFormation template sections

CloudFormation templates are composed of one or more sections, including:

  • Format version
  • Resources
  • Description
  • Metadata
  • Parameters
  • Mappings
  • Conditions
  • Rules
  • Transform
  • Outputs

1. Resources

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 Ref and the 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:

2. Parameters

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

3. Mappings

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

4. Outputs

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

5. Metadata

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"

6. Conditions

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]

7. Transform

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.

8. Rules

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:

 

cloudformation template parameters

9. Description

The Description section of the CloudFormation templates allows for a textual description of the template and its resources.

10. Format Version

The optional AWSTemplateFormatVersion section identifies the current template format version. The latest template format version is 2010-09-09.

Benefits of using CloudFormation templates

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.

AWS CloudFormation template example

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:

  1. As a first step, we defined the Template Version and Description.
  2. Next, we define different parameters such as EnvironmentName and InstanceType, among others.
  3. Right after, we define our actual resources along with their configuration, including VPC, EC2, Public Subnet, Internet Gateway, and Security Group.
  4. 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.`

cloudformation template reference

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:

cloudformation template state

On the `Events` tab, you can see the actual events of the deployment as they happen:

aws cloudformation stack events

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:

aws cloudformation stack output

CloudFormation templates best practices

Here are some best practices for creating and using CloudFormation templates.

  1. Store your CloudFormation templates in Git repositories or S3.
  2. 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.
  3. Make your templates reusable and dynamic using parameters, intrinsic functions, and conditionals.
  4. Implement an elaborate naming strategy. Use logical names for your resources that clearly state their purpose.
  5. Because your resources will depend on each other, leverage the DependsOn attribute to ensure resources are created in the correct order.
  6. For complex architectures and environments, explore advanced features and functionalities such as nested stacks and StackSets.
  7. Use linting and validation tools such as cfn-lint.

Can I use CloudFormation with Spacelift?

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.

Key points

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.

Learn more

The Practitioner’s Guide to Scaling Infrastructure as Code

Transform your IaC management to scale

securely, efficiently, and productively

into the future.

ebook global banner
Share your data and download the guide