DEV Community

Arash Zand
Arash Zand

Posted on • Originally published at Medium

Learning AWS CloudFormation: A Practical Introduction

AWS CloudFormation lets you manage your AWS infrastructure using code. Instead of setting up resources by hand, you write a template in JSON or YAML and let CloudFormation handle the rest. This guide covers the core concepts, stack types, lifecycle states, and real troubleshooting patterns from hands-on experience.


Why CloudFormation?

  • Automation — define once, deploy anywhere, no manual clicking
  • Consistency — same template = same environment every time
  • Version control — templates are code, store them in Git
  • Cost control — delete a whole stack with one command, no orphaned resources
  • Scalability — update templates to grow infrastructure as the project grows

Types of Stacks

1. Single Stack

All resources in one template. Good for small projects.

AWSTemplateFormatVersion: '2010-09-09'
Resources:
  BlogSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group for blog app
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0

  BlogInstanceRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: ec2.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess

  BlogEC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: t2.micro
      ImageId: ami-0c55b159cbfafe1f0
      SecurityGroupIds:
        - !Ref BlogSecurityGroup
      IamInstanceProfile: !Ref BlogInstanceProfile

  BlogStaticBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub blog-static-files-${AWS::StackName}
Enter fullscreen mode Exit fullscreen mode

2. Nested Stacks

Split large templates into modular child stacks. A parent stack references child stacks stored in S3. Good for separating networking, compute, and storage concerns.

Parent template:

AWSTemplateFormatVersion: '2010-09-09'
Parameters:
  VpcCidr:
    Type: String
    Default: 10.1.0.0/16

Resources:
  NetworkStack:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: https://s3.amazonaws.com/my-bucket/ecommerce-network.yaml
      Parameters:
        VpcCidr: !Ref VpcCidr

  AppServerStack:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: https://s3.amazonaws.com/my-bucket/ecommerce-app.yaml
      Parameters:
        SubnetId: !GetAtt NetworkStack.Outputs.PublicSubnetId
        SecurityGroupId: !GetAtt NetworkStack.Outputs.AppSecurityGroupId

  DatabaseStack:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: https://s3.amazonaws.com/my-bucket/ecommerce-database.yaml
      Parameters:
        SubnetId: !GetAtt NetworkStack.Outputs.PrivateSubnetId
        SecurityGroupId: !GetAtt NetworkStack.Outputs.DBSecurityGroupId
Enter fullscreen mode Exit fullscreen mode

Child network template (ecommerce-network.yaml):

AWSTemplateFormatVersion: '2010-09-09'
Parameters:
  VpcCidr:
    Type: String

Resources:
  EcommerceVPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VpcCidr

  PublicSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref EcommerceVPC
      CidrBlock: 10.1.1.0/24

  AppSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group for app servers
      VpcId: !Ref EcommerceVPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0

  DBSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group for database
      VpcId: !Ref EcommerceVPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 3306
          ToPort: 3306
          SourceSecurityGroupId: !Ref AppSecurityGroup

Outputs:
  PublicSubnetId:
    Value: !Ref PublicSubnet
  PrivateSubnetId:
    Value: !Ref PrivateSubnet
  AppSecurityGroupId:
    Value: !Ref AppSecurityGroup
  DBSecurityGroupId:
    Value: !Ref DBSecurityGroup
Enter fullscreen mode Exit fullscreen mode

Lesson learned: A parent stack with 8 child stacks took 25 minutes to deploy vs 12 minutes for a single stack. Keep nesting manageable.

3. Stacks with Exports

Share values between independent stacks using Outputs + Export.

Network stack exports a subnet ID:

Outputs:
  SubnetIdExport:
    Value: !Ref AppSubnet
    Export:
      Name: AppSubnetExport
Enter fullscreen mode Exit fullscreen mode

Storage stack imports it:

Resources:
  DBSubnetGroup:
    Type: AWS::RDS::DBSubnetGroup
    Properties:
      DBSubnetGroupDescription: Subnet group for RDS
      SubnetIds:
        - !ImportValue AppSubnetExport
Enter fullscreen mode Exit fullscreen mode

Stack Lifecycle States

Understanding states helps you debug what's happening and why.

State Meaning
CREATE_IN_PROGRESS Resources are being provisioned
CREATE_COMPLETE Stack created successfully
CREATE_FAILED Something went wrong during creation
UPDATE_IN_PROGRESS A change is being applied
UPDATE_COMPLETE Update succeeded
UPDATE_ROLLBACK_IN_PROGRESS Update failed, rolling back
UPDATE_ROLLBACK_COMPLETE Rollback finished, back to previous state
DELETE_IN_PROGRESS Stack deletion in progress
DELETE_COMPLETE All resources removed
REVIEW_IN_PROGRESS Change set being validated
ROLLBACK_IN_PROGRESS Initial creation failed, cleaning up
ROLLBACK_COMPLETE Cleanup done after failed creation

Troubleshooting Common Issues

1. Syntax errors

Validate before deploying:

aws cloudformation validate-template \
  --template-body file://template.yaml \
  --region us-west-2
Enter fullscreen mode Exit fullscreen mode

2. Wrong resource settings

Check the AWS CloudFormation docs for valid property names. A single typo lands you in CREATE_FAILED.

3. Stuck in REVIEW_IN_PROGRESS

Cancel the update or delete the change set:

aws cloudformation cancel-update-stack \
  --stack-name AppBase-US-West-2 \
  --region us-west-2

aws cloudformation delete-change-set \
  --stack-name AppBase-US-West-2 \
  --change-set-name FailedChange \
  --region us-west-2
Enter fullscreen mode Exit fullscreen mode

Nuclear option if needed:

aws cloudformation delete-stack \
  --stack-name AppBase-US-West-2 \
  --region us-west-2
Enter fullscreen mode Exit fullscreen mode

4. Pipeline failures

If a CI/CD pipeline (GitHub Actions, Azure DevOps) fails mid-deploy, add a cleanup step that deletes failed change sets before retrying. Otherwise the next run will fail immediately on the stale change set.

5. Dependency issues

Use DependsOn when a resource needs another to exist first:

Resources:
  MyLambda:
    Type: AWS::Lambda::Function
    DependsOn: MyDynamoDBStream
    Properties:
      ...
Enter fullscreen mode Exit fullscreen mode

Tips for Getting Started

  1. Start simple — one EC2 instance, get it deploying cleanly before adding complexity
  2. Use parameters — make templates reusable across environments from day one
  3. Test on a disposable stack — never test changes directly on production
  4. Watch the Events tab — it's the most useful debugging surface in the console
  5. Plan for rollback — always include cleanup steps in your pipeline

Conclusion

CloudFormation is foundational infrastructure-as-code on AWS. Single stacks for simplicity, nested stacks for modularity, exports for cross-stack sharing. Know the lifecycle states, validate templates before deploying, and build rollback handling into your pipelines from the start.


Originally published on Medium.

Top comments (0)