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 theAdministratorAccesspolicy for testing). - AWS CLI v2 installed and configured (
aws configurewith access key). - Default AWS region:
us-east-1(changeable via--region). - Basic knowledge of YAML and AWS networking.
cfn-linttool for validation:pip install cfn-lint.
Basic S3 Bucket Template
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
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
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.PublicIpIntegrates 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
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
#!/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-setto preview diffs. - Drift detection:
aws cloudformation detect-stack-driftfor audits. - Version templates in Git + CodePipeline for CI/CD.
Common Errors to Avoid
- Missing dependencies: Always use
DependsOnorCreationPolicyfor 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
- Official docs: AWS CloudFormation.
- Advanced: CDK (IaC in TS/Python) or hybrid Terraform.
- Learni DevOps AWS Training for Architect certification.
- GitHub repo examples: aws-samples/cloudformation-templates.