Skip to content
Learni
View all tutorials
AWS

How to Deploy an AWS CloudFormation Stack in 2026

Lire en français

Introduction

AWS CloudFormation revolutionizes Infrastructure as Code (IaC) by modeling your cloud resources as declarative templates. In 2026, with the rise of hybrid multi-cloud environments, mastering CloudFormation is essential for DevOps teams: it ensures reproducibility, versioning, and auditability of deployments. This intermediate tutorial guides you step-by-step through creating a complete stack including VPC, subnets, a secure EC2 instance, and an S3 bucket. You'll learn to use dynamic parameters, outputs for interoperability, and advanced CLI commands. By the end, you'll deploy scalable infrastructure in 10 minutes, avoiding classic pitfalls like circular dependencies. Ideal for scaling apps without downtime. (128 words)

Prerequisites

  • Active AWS account with IAM permissions: cloudformation:, ec2:, s3:, vpc: (attach the AdministratorAccess policy for testing).
  • AWS CLI v2 installed and configured (aws configure with access key).
  • Default AWS region: us-east-1 (changeable via --region).
  • Basic knowledge of YAML and AWS networking.
  • cfn-lint tool for validation: pip install cfn-lint.

Basic S3 Bucket Template

s3-bucket.yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Bucket S3 avec versioning et chiffrement'

Resources:
  MyS3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub 'mon-bucket-${AWS::AccountId}-${AWS::Region}'
      VersioningConfiguration:
        Status: Enabled
      BucketEncryption:
        ServerSideEncryptionConfiguration:
        - ServerSideEncryptionByDefault:
            SSEAlgorithm: AES256

Outputs:
  BucketName:
    Description: Nom du bucket créé
    Value: !Ref MyS3Bucket
    Export:
      Name: !Sub '${AWS::StackName}-BucketName'

This template creates a unique S3 bucket (via !Sub with AccountId and Region) with versioning for audits and default AES256 encryption. The exported output allows referencing this bucket in other stacks. Pitfall to avoid: globally unique bucket names, hence the forced uniqueness; test with aws s3 ls after deployment.

CloudFormation Template Basics

Every template starts with AWSTemplateFormatVersion for compatibility (2010-09-09 is stable). The Resources section declares AWS primitives (S3::Bucket here). Use intrinsic functions like !Ref (resource reference), !Sub (substitution), and !GetAtt (attributes). Outputs expose values for cross-stack references. Always validate with cfn-lint s3-bucket.yaml before deployment to catch YAML syntax or logical errors.

VPC and Subnets Template

vpc-network.yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: 'VPC avec public/private subnets'

Parameters:
  VpcCidr:
    Type: String
    Default: 10.0.0.0/16
    Description: CIDR du VPC

Resources:
  MyVPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VpcCidr
      EnableDnsHostnames: true
      EnableDnsSupport: true
      Tags:
      - Key: Name
        Value: !Sub '${AWS::StackName}-VPC'

  PublicSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref MyVPC
      CidrBlock: 10.0.1.0/24
      AvailabilityZone: !Select [0, !GetAZs '']
      MapPublicIpOnLaunch: true
      Tags:
      - Key: Name
        Value: !Sub '${AWS::StackName}-Public'

  InternetGateway:
    Type: AWS::EC2::InternetGateway

  AttachGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref MyVPC
      InternetGatewayId: !Ref InternetGateway

  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref MyVPC

  PublicRoute:
    Type: AWS::EC2::Route
    DependsOn: AttachGateway
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  PublicSubnetRouteTableAssoc:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet
      RouteTableId: !Ref PublicRouteTable

Outputs:
  VpcId:
    Value: !Ref MyVPC
    Export:
      Name: !Sub '${AWS::StackName}-VpcId'
  PublicSubnetId:
    Value: !Ref PublicSubnet
    Export:
      Name: !Sub '${AWS::StackName}-PublicSubnetId'

This template deploys a /16 VPC with a public subnet routed to an Internet Gateway for internet access. The VpcCidr parameter allows customization; DependsOn handles dependencies. Exported outputs make EC2 integration easy. Watch for the AZ pitfall: !GetAZs '' lists available AZs; deploy in us-east-1 for reliability.

Managing Networks and Dependencies

DependsOn orders creation (IGW before route). Use Tags to filter in the console. For private subnets, add a NAT Gateway (costs ~$30/month). Test connectivity: aws ec2 describe-route-tables --route-table-ids $(aws cloudformation describe-stacks --stack-name test-vpc --query 'Stacks[0].Outputs[?OutputKey==PublicSubnetId].OutputValue' --output text).

Secure EC2 Instance in VPC

ec2-instance.yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: 'EC2 Ubuntu dans VPC avec SG'

Parameters:
  VpcId:
    Type: AWS::EC2::VPC::Id
  PublicSubnetId:
    Type: AWS::EC2::Subnet::Id
  KeyName:
    Type: AWS::EC2::KeyPair::KeyName
    Description: Nom de la clé SSH (créez-en une avant)

Resources:
  WebSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Accès SSH/HTTP
      VpcId: !Ref VpcId
      SecurityGroupIngress:
      - IpProtocol: tcp
        FromPort: 22
        ToPort: 22
        CidrIp: 0.0.0.0/0
      - IpProtocol: tcp
        FromPort: 80
        ToPort: 80
        CidrIp: 0.0.0.0/0

  WebServer:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: t3.micro
      ImageId: ami-0c02fb55956c7d316  # Ubuntu 22.04 us-east-1, vérifiez ami actuelle
      KeyName: !Ref KeyName
      SubnetId: !Ref PublicSubnetId
      SecurityGroupIds:
      - !Ref WebSecurityGroup
      UserData:
        Fn::Base64: !Sub |
          #!/bin/bash
          apt-get update -y
          apt-get install -y apache2
          systemctl start apache2
          echo "<h1>CloudFormation OK!</h1>" > /var/www/html/index.html

Outputs:
  InstancePublicIp:
    Description: IP publique de l'EC2
    Value: !GetAtt WebServer.PublicIp

Integrates with VPC via parameters; SG allows worldwide SSH/HTTP (restrict in production). UserData bootstraps Apache automatically. Ubuntu AMI specific to us-east-1 (find via AWS console). Pitfall: no KeyName means no SSH access; test with ssh -i key.pem ubuntu@IP. Output IP for monitoring.

EC2 Integration and UserData

UserData runs at boot (Base64 encoded). Fn::Base64 and !Sub enable interpolation. For dynamic AMIs: AWS::SSM::Parameter::Value with /aws/service/ami-amazon-linux-latest. Scale with AutoScalingGroup for production.

Complete Template with S3 and Outputs

full-stack.yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Stack complète VPC+EC2+S3'

Parameters:
  KeyName:
    Type: AWS::EC2::KeyPair::KeyName

Resources:
  # Intégrez VPC/EC2/S3 ici (combinaison des précédents)
  MyS3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub 'fullstack-${AWS::AccountId}'
  # ... (ajoutez VPC, EC2 comme ci-dessus)

Outputs:
  WebsiteURL:
    Value: !Sub 'http://${WebServer.PublicIp}'
  BucketURL:
    Value: !Sub 'https://${MyS3Bucket}.s3.amazonaws.com'

Consolidated template reusing cross-stack outputs (import VpcId via Fn::ImportValue). Add all resources. For full stack: copy VPC+EC2+S3. Composite outputs for CI/CD. Pitfall: avoid circular refs with explicit DependsOn.

CLI Deployment Script

deploy.sh
#!/bin/bash
STACK_NAME="demo-stack"
TEMPLATE="full-stack.yaml"

# Create or update stack
if aws cloudformation describe-stacks --stack-name $STACK_NAME &>/dev/null; then
  echo "Update en cours..."
  aws cloudformation update-stack \
    --stack-name $STACK_NAME \
    --template-body file://$TEMPLATE \
    --capabilities CAPABILITY_IAM \
    --parameters ParameterKey=KeyName,ParameterValue=votre-key
else
  echo "Création stack..."
  aws cloudformation create-stack \
    --stack-name $STACK_NAME \
    --template-body file://$TEMPLATE \
    --capabilities CAPABILITY_IAM \
    --parameters ParameterKey=KeyName,ParameterValue=votre-key
fi

# Wait for completion
aws cloudformation wait stack-create-complete --stack-name $STACK_NAME || aws cloudformation wait stack-update-complete --stack-name $STACK_NAME

echo "Outputs:"
aws cloudformation describe-stacks --stack-name $STACK_NAME --query 'Stacks[0].Outputs'

Idempotent script: auto create/update. --capabilities CAPABILITY_IAM for auto-created roles. wait avoids race conditions. Replace votre-key. Run chmod +x deploy.sh && ./deploy.sh. For delete: aws cloudformation delete-stack --stack-name demo-stack.

Best Practices

  • Modularize: Use nested stacks (AWS::CloudFormation::Stack) for reusability.
  • Default parameters and constraints (AllowedValues) for self-service.
  • ChangeSets before updates: aws cloudformation create-change-set to preview diffs.
  • Drift detection: aws cloudformation detect-stack-drift for audits.
  • Version templates in Git + CodePipeline for CI/CD.

Common Errors to Avoid

  • Missing dependencies: Always use DependsOn or CreationPolicy for slow resources (EC2).
  • Forgotten IAM capabilities: ROLLBACK error without --capabilities.
  • Region/AMI mismatch: Check AMI with aws ec2 describe-images.
  • Duplicate bucket names: Global uniqueness, force with !Sub.

Next Steps