Write CloudFormation Templates

Write CloudFormation templates for AWS resources — AWS Cloud & DevOps with Python.

Focus: write cloudformation templates for aws resources

Sponsored

You’ve built the Python app, wrapped it in a container, and pushed it to ECR. Now you need a repeatable, reviewable way to spin up the supporting AWS infrastructure — VPC, load balancer, database, permissions — without clicking through the console at 2 a.m. If you’ve ever deployed by hand or with a pile of shell scripts, you know the pain: drift between environments, missing security group rules, and "it worked on my machine" syndrome. This lesson shows you how to write CloudFormation templates for AWS resources — a single source of truth that turns your infrastructure into code you can version, review, and deploy with confidence.

The problem this lesson solves

Manual infrastructure management is fragile and slow. You or your team might be doing any of this today:

  • Clicking through the AWS Management Console to create EC2 instances, S3 buckets, or RDS databases.
  • Copy-pasting shell commands from an old README that no one fully trusts.
  • Discovering that staging has a different security group than production, and no one knows why.

These approaches don’t scale. They introduce configuration drift, make audits painful, and turn rollbacks into archaeology. Infrastructure as Code (IaC) fixes this by describing your AWS resources in a declarative file. AWS CloudFormation is AWS’s native IaC service — it turns a template (JSON or YAML) into a stack, and then creates, updates, or deletes all the resources in that stack as a single unit.

Pro tip: If you’ve used Terraform, the mental shift is small. CloudFormation is AWS-native, deeply integrated with IAM and CloudTrail, and free (you only pay for the resources it creates).

Core concept / mental model

Think of a CloudFormation template as a blueprint for your cloud infrastructure. Just as an architect’s blueprint describes every wall, door, and electrical outlet, your template describes every AWS resource — its type, properties, and dependencies. CloudFormation acts as the general contractor that reads the blueprint and builds everything in the right order.

Here’s the high-level anatomy of a template:

  • AWSTemplateFormatVersion — (optional) identifies the template version.
  • Description — a human-readable summary.
  • Parameters — inputs you pass at deployment time (e.g., environment name, instance type).
  • Resources — the core section: every AWS resource you want to create.
  • Outputs — values you want to expose after the stack is deployed (e.g., the endpoint URL).

The key insight is declarative management: you say what you want, not how to build it. CloudFormation determines the order — it knows an EC2 instance depends on its security group, so it creates the group first.

How it works step by step

Writing a CloudFormation template follows a predictable workflow. Let’s break it down:

  1. Define the resource types. Each AWS resource has a logical name (your choice) and a Type (e.g., AWS::EC2::Instance, AWS::S3::Bucket). You’ll reference these types constantly.

  2. Set properties. Every resource type supports a set of properties. For an EC2 instance, you set ImageId, InstanceType, and SecurityGroupIds. For an S3 bucket, you might set BucketName and VersioningConfiguration.

  3. Wire dependencies with references. Use the !Ref intrinsic function to reference another resource’s logical name (which returns its ID or ARN), and !GetAtt to fetch an attribute (like an instance’s public IP).

  4. Add parameters for flexibility. Instead of hardcoding values, use Parameters so you can deploy the same template to dev, staging, and prod with different inputs.

  5. Declare outputs. Expose useful values like the database endpoint or load balancer DNS name so other stacks or scripts can consume them.

  6. Validate and deploy. Use the AWS CLI or console to validate your template, then create a stack. CloudFormation handles provisioning, rollback on failure, and cleanup.

Hands-on walkthrough

Let’s put this into practice. We’ll write a template that creates an S3 bucket and an EC2 instance in a new VPC — a minimal but realistic setup.

Example 1: A simple S3 bucket

Start with the simplest possible template.

AWSTemplateFormatVersion: '2010-09-09'
Description: A simple S3 bucket

Resources:
  MyBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: my-unique-bucket-name-12345
      VersioningConfiguration:
        Status: Enabled

Outputs:
  BucketName:
    Value: !Ref MyBucket
    Description: Name of the created bucket

Save as s3-bucket.yaml. Deploy with AWS CLI:

aws cloudformation deploy \
  --template-file s3-bucket.yaml \
  --stack-name my-s3-stack

Pro tip: aws cloudformation deploy is the recommended CLI command — it handles change sets and rollbacks automatically. After deployment, you’ll see the bucket in your account, versioning enabled.

Example 2: EC2 with a security group and parameterized instance type

Now let’s make it more realistic: an EC2 instance that only allows SSH from your IP, with the instance type passed as a parameter.

AWSTemplateFormatVersion: '2010-09-09'
Description: EC2 instance with a security group

Parameters:
  InstanceType:
    Type: String
    Default: t2.micro
    AllowedValues:
      - t2.micro
      - t3.micro
    Description: EC2 instance type

  KeyName:
    Type: AWS::EC2::KeyPair::KeyName
    Description: Existing key pair to SSH into the instance

  SSHLocation:
    Type: String
    Default: 0.0.0.0/0
    Description: IP range allowed for SSH

Resources:
  InstanceSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Allow SSH from specified IP
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: !Ref SSHLocation

  MyEC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: ami-0abcdef1234567890   # Replace with a valid AMI for your region
      InstanceType: !Ref InstanceType
      KeyName: !Ref KeyName
      SecurityGroupIds:
        - !Ref InstanceSecurityGroup

Outputs:
  InstancePublicIP:
    Value: !GetAtt MyEC2Instance.PublicIp
    Description: Public IP of the EC2 instance

Deploy it:

aws cloudformation deploy \
  --template-file ec2.yaml \
  --stack-name my-ec2-stack \
  --parameter-overrides InstanceType=t3.micro KeyName=my-key

Expected output: CloudFormation creates the security group first, then the instance, and prints the public IP in the stack outputs.

Example 3: Python-friendly — generate a template with a script

As a Python developer, you can leverage Python to generate or manage your templates. Here’s a small script that reads a YAML template and prints its resources — useful for validation or documentation.

import yaml
from pathlib import Path

def load_template(path: str) -> dict:
    """Load a CloudFormation template from YAML."""
    with open(path) as f:
        return yaml.safe_load(f)

if __name__ == "__main__":
    template = load_template("ec2.yaml")
    print("Resources in template:")
    for logical_id, resource in template.get("Resources", {}).items():
        print(f"  - {logical_id}: {resource['Type']}")

Save as inspect_template.py, then run:

python inspect_template.py

Output:

Resources in template:
  - InstanceSecurityGroup: AWS::EC2::SecurityGroup
  - MyEC2Instance: AWS::EC2::Instance

This is a tiny demo, but in real projects you can use Python to lint templates, inject parameters, or even generate resource blocks programmatically.

Compare options / when to choose what

CloudFormation isn’t your only IaC option. Here’s how it stacks up against the most common alternatives:

Tool Language AWS-native Best for
CloudFormation YAML/JSON Yes Teams fully on AWS; deep integration with IAM, CloudTrail, and StackSets
Terraform HCL No (multi-cloud) Multi-cloud or existing HashiCorp workflows
AWS CDK Python/TypeScript/etc. Yes Developers who prefer real programming languages over YAML
SAM YAML (serverless extensions) Yes Serverless applications (Lambda, API Gateway)

When to choose CloudFormation:

  • You want a single tool for all AWS resources, not just serverless.
  • You need native rollback and drift detection.
  • You want to avoid managing state files (CloudFormation does it for you).

When to consider something else:

  • You need multi-cloud support → Terraform.
  • You want to write infrastructure in Python and use object-oriented patterns → AWS CDK (which still synthesizes CloudFormation templates!).
  • You’re doing pure serverless → SAM is more concise.

Key point: No matter which you choose, understanding CloudFormation templates gives you a foundation. Even CDK compiles down to CloudFormation, so debugging a CDK deployment often means reading the generated template.

Troubleshooting & edge cases

Here are the most common problems you’ll hit when writing CloudFormation templates, and how to fix them.

1. "Template format error: YAML not well-formed"

This means your YAML has indentation or syntax errors. Check:

  • Indentation must be consistent (spaces, not tabs).
  • Colons must be followed by a space before the value (unless it’s a key at the end).
  • Use a YAML linter or python -c "import yaml, sys; yaml.safe_load(sys.stdin)" to validate.

2. "Resource creation cancelled" or stack rolls back

CloudFormation rolls back automatically if a resource fails. Common causes:

  • Invalid AMI ID — AMIs are region-specific; your ami-0abcdef... may not exist. Find a valid one with aws ec2 describe-images.
  • Insufficient IAM permissions — your deploy user must have cloudformation:CreateStack and permissions for each resource.
  • Resource limit — e.g., you exceeded the VPC limit in the region.

Check the Events tab in the CloudFormation console — it shows exactly which resource failed and why.

3. "No updates are to be performed"

You deployed but nothing changed. This usually means you didn’t modify the template or parameters. CloudFormation compares changes and skips if nothing differs. Double-check your template file path and that you passed new parameter values.

4. !Ref is not a number

If you try to use !Ref where an integer is expected (e.g., Port), CloudFormation might complain because !Ref returns a string. Use the !Select or !GetAtt correctly, or pass the value as a parameter with the right Type (e.g., Type: Number).

5. Hardcoded values that break reuse

If you hardcode an S3 bucket name, it must be globally unique. Use !Sub '${AWS::StackName}-bucket' instead to avoid collisions.

What you learned & what's next

You now know how to write CloudFormation templates for AWS resources — the core skill for treating infrastructure as code on AWS. You learned:

  • The anatomy of a template: Parameters, Resources, Outputs.
  • How to define real resources like S3 buckets and EC2 instances.
  • How to use intrinsic functions !Ref and !GetAtt to wire resources together.
  • How to deploy with aws cloudformation deploy.
  • How to compare CloudFormation with Terraform, CDK, and SAM.
  • How to troubleshoot common template errors.

This foundational knowledge unlocks the next step in your DevOps journey: managing stacks at scale. Next, you’ll learn how to update stacks safely using change sets, handle drift detection, and structure large templates with nested stacks or modules. By mastering CloudFormation, you’re building the muscle you need for advanced infrastructure automation — and for tools like AWS CDK that sit on top of it.

Practice recap

Take the EC2 template from this lesson and extend it: add an S3 bucket and grant the EC2 instance an IAM role with read access to that bucket. Deploy it to your account, then try deleting the stack and confirm all resources are removed — that’s CloudFormation’s magic.

Common mistakes

  • Hardcoding resource names that aren't globally unique (e.g., S3 bucket names) — causes deployment failures; use !Sub '${AWS::StackName}-bucket' instead.
  • Forgetting that AMI IDs are region-specific — a template that works in us-east-1 fails in eu-central-1; avoid hardcoding AMIs, use SSM parameters.
  • Misplacing indentation in YAML — a single tab or missing space makes the whole template invalid; validate with a linter before deploying.
  • Not using Parameters for values that change between environments — you end up editing the template for every deployment instead of passing --parameter-overrides.
  • Assuming CloudFormation creates resources in the order you write them — it manages dependencies for you, but you must reference them with !Ref or !GetAtt correctly or you'll get circular dependency errors.

Variations

  1. Use AWS CDK to define infrastructure in Python (or TypeScript) — it compiles to CloudFormation templates and adds programming constructs like loops and conditionals.
  2. Use Terraform if you need multi-cloud support or a different state management model — same concepts but HCL syntax.
  3. Use AWS SAM for serverless-only apps — it's a CloudFormation extension with simplified syntax for Lambda, API Gateway, and DynamoDB.

Real-world use cases

  • Provision a full staging environment (VPC, EC2, RDS, load balancer) with one command and replicate it for production using the same template.
  • Automate a secure S3 bucket with versioning and encryption for storing application backups — deployed as part of a CI/CD pipeline.
  • Create a reusable template that spins up a bastion host and security groups for auditors to access private subnets temporarily.

Key takeaways

  • CloudFormation turns infrastructure into code — declarative YAML/JSON templates that create, update, and delete AWS resources as a single unit.
  • A template has four main sections: Parameters (inputs), Resources (what you create), Outputs (values you expose), and optional Conditions.
  • Use !Ref to reference resource logical IDs and !GetAtt to fetch attributes — this wires dependencies so CloudFormation builds in the right order.
  • AWS CloudFormation provides automatic rollback and drift detection, making it safer than manual console changes.
  • While alternatives like Terraform and CDK exist, CloudFormation is the AWS-native baseline — even CDK generates it under the hood.
  • Use parameters for anything that changes between environments — hardcoding values is the fastest path to failure.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.