AWS CloudFormation: Infrastructure as Code Complete Guide (2026)

CloudFormation is AWS's native Infrastructure as Code service. Define your entire AWS infrastructure in YAML or JSON templates, and CloudFormation handles creation, updates and deletion in the correct dependency order. Unlike CDK or Terraform, CloudFormation templates are natively understood by AWS and have the broadest service coverage — every new AWS service gets CloudFormation support on day one.

1. Template Anatomy

AWSTemplateFormatVersion: '2010-09-09'
Description: 'MyApp production infrastructure'

Parameters:      # Input values at deploy time
  ...

Mappings:        # Static lookup tables (AMI IDs per region)
  ...

Conditions:      # Boolean conditions (enable/disable resources)
  ...

Resources:       # REQUIRED — AWS resources to create
  ...

Outputs:         # Export values for cross-stack use
  ...

2. Parameters and Mappings

Parameters:
  Environment:
    Type: String
    Default: staging
    AllowedValues: [staging, production]
    Description: Deployment environment

  InstanceType:
    Type: String
    Default: t3.medium
    AllowedValues: [t3.small, t3.medium, t3.large, m6i.large]

  DBPassword:
    Type: String
    NoEcho: true    # Masked in console and CLI output
    MinLength: 8
    Description: RDS master password

Mappings:
  RegionAMI:
    us-east-1:
      AmazonLinux2023: ami-0abcdef1234567890
    us-west-2:
      AmazonLinux2023: ami-0fedcba9876543210

Conditions:
  IsProduction: !Equals [!Ref Environment, production]
  IsNotProduction: !Not [!Condition IsProduction]

3. Resources

Resources:
  AppSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: App server security group
      VpcId: !Ref VpcId
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 8080
          ToPort: 8080
          SourceSecurityGroupId: !Ref ALBSecurityGroup

  AppLaunchTemplate:
    Type: AWS::EC2::LaunchTemplate
    Properties:
      LaunchTemplateData:
        ImageId: !FindInMap [RegionAMI, !Ref AWS::Region, AmazonLinux2023]
        InstanceType: !Ref InstanceType
        SecurityGroupIds: [!Ref AppSecurityGroup]
        IamInstanceProfile:
          Arn: !GetAtt AppInstanceProfile.Arn
        UserData:
          Fn::Base64: !Sub |
            #!/bin/bash
            yum update -y
            aws s3 cp s3://${ArtifactBucket}/app.jar /opt/app/
            systemctl start myapp

  AppAutoScalingGroup:
    Type: AWS::AutoScaling::AutoScalingGroup
    Properties:
      LaunchTemplate:
        LaunchTemplateId: !Ref AppLaunchTemplate
        Version: !GetAtt AppLaunchTemplate.LatestVersionNumber
      MinSize: !If [IsProduction, '2', '1']
      MaxSize: !If [IsProduction, '10', '3']
      TargetGroupARNs: [!Ref AppTargetGroup]
      VPCZoneIdentifier: !Ref PrivateSubnets
    UpdatePolicy:
      AutoScalingRollingUpdate:
        MinInstancesInService: 1
        MaxBatchSize: 2

4. Intrinsic Functions

FunctionShort FormPurpose
Ref!RefReference parameter or resource ID
Fn::GetAtt!GetAttGet resource attribute (e.g., ARN)
Fn::Sub!SubString substitution with variable replacement
Fn::Join!JoinJoin list with delimiter
Fn::Select!SelectSelect item from list by index
Fn::FindInMap!FindInMapLook up value in Mappings
Fn::If!IfConditional value based on Condition
Fn::ImportValue!ImportValueImport exported output from another stack
# Examples
BucketArn: !GetAtt MyBucket.Arn
LambdaInvokeArn: !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path/functions/${MyFunction.Arn}/invocations"
DBEndpoint: !Join [':', [!GetAtt Database.Endpoint.Address, !GetAtt Database.Endpoint.Port]]

5. Outputs and Cross-Stack References

# Stack A — exports values
Outputs:
  VpcId:
    Description: VPC ID for application stacks
    Value: !Ref AppVPC
    Export:
      Name: !Sub "${AWS::StackName}-VpcId"

  PrivateSubnets:
    Value: !Join [',', [!Ref PrivateSubnet1, !Ref PrivateSubnet2]]
    Export:
      Name: !Sub "${AWS::StackName}-PrivateSubnets"

# Stack B — imports values from Stack A
Resources:
  AppInstance:
    Type: AWS::EC2::Instance
    Properties:
      SubnetId: !Select [0, !Split [',', !ImportValue 'network-stack-PrivateSubnets']]

6. Nested Stacks

# Parent stack — composes nested stacks
Resources:
  NetworkStack:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: !Sub "https://s3.amazonaws.com/${TemplateBucket}/network.yaml"
      Parameters:
        Environment: !Ref Environment

  AppStack:
    Type: AWS::CloudFormation::Stack
    DependsOn: NetworkStack
    Properties:
      TemplateURL: !Sub "https://s3.amazonaws.com/${TemplateBucket}/app.yaml"
      Parameters:
        VpcId: !GetAtt NetworkStack.Outputs.VpcId
        PrivateSubnets: !GetAtt NetworkStack.Outputs.PrivateSubnets

7. Change Sets

# Create a change set to preview changes before applying
aws cloudformation create-change-set \
  --stack-name myapp-production \
  --change-set-name update-instance-type \
  --template-body file://template.yaml \
  --parameters ParameterKey=InstanceType,ParameterValue=m6i.large

# Review the change set
aws cloudformation describe-change-set \
  --stack-name myapp-production \
  --change-set-name update-instance-type

# Execute (apply changes)
aws cloudformation execute-change-set \
  --stack-name myapp-production \
  --change-set-name update-instance-type
Pro Tip: Always use change sets in production — they show you exactly what will be added, modified, or deleted before any changes happen. Never directly update production stacks without reviewing a change set first.

8. StackSets (Multi-Account, Multi-Region)

# Deploy the same stack to multiple accounts and regions
aws cloudformation create-stack-set \
  --stack-set-name baseline-security \
  --template-url https://s3.amazonaws.com/mybucket/security-baseline.yaml \
  --permission-model SERVICE_MANAGED \
  --auto-deployment Enabled=true,RetainStacksOnAccountRemoval=false

# Deploy to all accounts in an AWS Organizations OU
aws cloudformation create-stack-instances \
  --stack-set-name baseline-security \
  --deployment-targets OrganizationalUnitIds=ou-root-abc123 \
  --regions us-east-1 us-west-2 eu-west-1 \
  --operation-preferences FailureTolerancePercentage=20,MaxConcurrentPercentage=25

Frequently Asked Questions

CloudFormation vs CDK vs Terraform — which should I use?

CloudFormation: native AWS, best service coverage, verbose YAML/JSON. CDK: write in Python/TypeScript/Java, generates CloudFormation, excellent for complex logic. Terraform: multi-cloud, HCL syntax, huge community, external state. For AWS-only teams: CDK is the modern choice. For multi-cloud: Terraform.

How do I prevent CloudFormation from deleting critical resources?

Set DeletionPolicy: Retain on resources like RDS, S3 buckets and EFS that contain data. Also enable Termination Protection on the stack itself: aws cloudformation update-termination-protection --stack-name mystack --enable-termination-protection.

What is stack drift and how do I detect it?

Drift occurs when someone manually modifies a CloudFormation-managed resource outside of CloudFormation. Run drift detection: aws cloudformation detect-stack-drift --stack-name mystack. Review drifted resources and either update CloudFormation to match reality or remediate the manual change.

How do I handle CloudFormation rollbacks?

When a stack update fails, CloudFormation automatically rolls back. If the rollback itself fails (ROLLBACK_FAILED state), you must manually fix the issue or skip the failing resources: aws cloudformation continue-update-rollback --stack-name mystack --resources-to-skip LogicalResourceId1.