| name | cloudformation-generate |
| description | Convert Terraform to CloudFormation or generate CFN templates
|
| shortcut | cfn |
| category | devops |
| difficulty | advanced |
| estimated_time | 3 minutes |
CloudFormation Template Generator
Generates AWS CloudFormation templates from scratch or helps convert Terraform configurations to CloudFormation (with limitations).
When to Use This
- Organization requires CloudFormation
- Need to use AWS StackSets
- Migrating from Terraform to CloudFormation
- AWS Service Catalog integration
- Prefer Terraform (use Terraform instead!)
- Multi-cloud infrastructure (CFN is AWS-only)
How It Works
You are a CloudFormation expert. When user runs /cloudformation-generate or /cfn:
-
Understand requirements:
- Starting from scratch OR converting from Terraform?
- What AWS resources? (VPC, EC2, RDS, S3, etc.)
- Parameters needed (make template reusable)?
- Outputs required?
-
Generate CloudFormation structure:
AWSTemplateFormatVersion: '2010-09-09'
Description: [template description]
Parameters:
Mappings:
Conditions:
Resources:
Outputs:
-
Apply CFN best practices:
- Use parameters for reusability
- Proper DependsOn for ordering
- Use !Ref and !GetAtt for references
- Add outputs for important values
- Use conditions for multi-environment
-
Provide deployment guide:
- CLI deployment commands
- Console deployment steps
- Parameter examples
- Stack update procedures
Output Format
## Deployment
### CLI:
```bash
aws cloudformation create-stack \
--stack-name my-stack \
--template-body file://template.yaml \
--parameters [params]
Console:
[Step-by-step instructions]
## Example
**User:** `/cfn`
**Request:** Create VPC with public/private subnets, similar to Terraform VPC module
**Output:**
```yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: 'VPC with public and private subnets across 2 AZs'
Parameters:
VPCName:
Description: Name of the VPC
Type: String
Default: MyVPC
VPCCIDR:
Description: CIDR block for VPC
Type: String
Default: 10.0.0.0/16
AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$
PublicSubnet1CIDR:
Description: CIDR for public subnet in AZ1
Type: String
Default: 10.0.1.0/24
PublicSubnet2CIDR:
Description: CIDR for public subnet in AZ2
Type: String
Default: 10.0.2.0/24
PrivateSubnet1CIDR:
Description: CIDR for private subnet in AZ1
Type: String
Default: 10.0.11.0/24
PrivateSubnet2CIDR:
Description: CIDR for private subnet in AZ2
Type: String
Default: 10.0.12.0/24
Environment:
Description: Environment name
Type: String
Default: dev
AllowedValues:
- dev
- staging
- production
Conditions:
IsProduction: !Equals [!Ref Environment, production]
Mappings:
AZMapping:
us-east-1:
AZ1: us-east-1a
AZ2: us-east-1b
us-west-2:
AZ1: us-west-2a
AZ2: us-west-2b
Resources:
# VPC
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VPCCIDR
EnableDnsHostnames: true
EnableDnsSupport: true
Tags:
- Key: Name
Value: !Ref VPCName
- Key: Environment
Value: !Ref Environment
# Internet Gateway
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Sub '${VPCName}-igw'
AttachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref VPC
InternetGatewayId: !Ref InternetGateway
# Public Subnets
PublicSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: !Ref PublicSubnet1CIDR
AvailabilityZone: !FindInMap [AZMapping, !Ref 'AWS::Region', AZ1]
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub '${VPCName}-public-1'
- Key: Type
Value: public
PublicSubnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: !Ref PublicSubnet2CIDR
AvailabilityZone: !FindInMap [AZMapping, !Ref 'AWS::Region', AZ2]
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub '${VPCName}-public-2'
- Key: Type
Value: public
# Private Subnets
PrivateSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: !Ref PrivateSubnet1CIDR
AvailabilityZone: !FindInMap [AZMapping, !Ref 'AWS::Region', AZ1]
Tags:
- Key: Name
Value: !Sub '${VPCName}-private-1'
- Key: Type
Value: private
PrivateSubnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: !Ref PrivateSubnet2CIDR
AvailabilityZone: !FindInMap [AZMapping, !Ref 'AWS::Region', AZ2]
Tags:
- Key: Name
Value: !Sub '${VPCName}-private-2'
- Key: Type
Value: private
# Elastic IPs for NAT Gateways
NatGateway1EIP:
Type: AWS::EC2::EIP
DependsOn: AttachGateway
Properties:
Domain: vpc
Tags:
- Key: Name
Value: !Sub '${VPCName}-nat-eip-1'
NatGateway2EIP:
Type: AWS::EC2::EIP
DependsOn: AttachGateway
Condition: IsProduction # Only create second NAT in production
Properties:
Domain: vpc
Tags:
- Key: Name
Value: !Sub '${VPCName}-nat-eip-2'
# NAT Gateways
NatGateway1:
Type: AWS::EC2::NatGateway
Properties:
AllocationId: !GetAtt NatGateway1EIP.AllocationId
SubnetId: !Ref PublicSubnet1
Tags:
- Key: Name
Value: !Sub '${VPCName}-nat-1'
NatGateway2:
Type: AWS::EC2::NatGateway
Condition: IsProduction
Properties:
AllocationId: !GetAtt NatGateway2EIP.AllocationId
SubnetId: !Ref PublicSubnet2
Tags:
- Key: Name
Value: !Sub '${VPCName}-nat-2'
# Public Route Table
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub '${VPCName}-public-rt'
PublicRoute:
Type: AWS::EC2::Route
DependsOn: AttachGateway
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
PublicSubnet1RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet1
RouteTableId: !Ref PublicRouteTable
PublicSubnet2RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet2
RouteTableId: !Ref PublicRouteTable
# Private Route Tables
PrivateRouteTable1:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub '${VPCName}-private-rt-1'
PrivateRoute1:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PrivateRouteTable1
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !Ref NatGateway1
PrivateSubnet1RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnet1
RouteTableId: !Ref PrivateRouteTable1
PrivateRouteTable2:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub '${VPCName}-private-rt-2'
PrivateRoute2:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PrivateRouteTable2
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !If [IsProduction, !Ref NatGateway2, !Ref NatGateway1]
PrivateSubnet2RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnet2
RouteTableId: !Ref PrivateRouteTable2
Outputs:
VPCID:
Description: VPC ID
Value: !Ref VPC
Export:
Name: !Sub '${AWS::StackName}-VPCID'
VPCCIDR:
Description: VPC CIDR block
Value: !GetAtt VPC.CidrBlock
PublicSubnets:
Description: Public subnet IDs (comma-separated)
Value: !Join [',', [!Ref PublicSubnet1, !Ref PublicSubnet2]]
Export:
Name: !Sub '${AWS::StackName}-PublicSubnets'
PrivateSubnets:
Description: Private subnet IDs (comma-separated)
Value: !Join [',', [!Ref PrivateSubnet1, !Ref PrivateSubnet2]]
Export:
Name: !Sub '${AWS::StackName}-PrivateSubnets'
NatGatewayIPs:
Description: NAT Gateway Elastic IPs
Value: !If
- IsProduction
- !Join [',', [!Ref NatGateway1EIP, !Ref NatGateway2EIP]]
- !Ref NatGateway1EIP
Deployment Instructions:
aws cloudformation validate-template --template-body file://vpc-template.yaml
aws cloudformation create-stack \
--stack-name myapp-vpc-dev \
--template-body file://vpc-template.yaml \
--parameters \
ParameterKey=VPCName,ParameterValue=myapp-dev \
ParameterKey=Environment,ParameterValue=dev
aws cloudformation create-stack \
--stack-name myapp-vpc-prod \
--template-body file://vpc-template.yaml \
--parameters \
ParameterKey=VPCName,ParameterValue=myapp-prod \
ParameterKey=Environment,ParameterValue=production
aws cloudformation describe-stacks --stack-name myapp-vpc-dev
aws cloudformation describe-stacks \
--stack-name myapp-vpc-dev \
--query 'Stacks[0].Outputs'
aws cloudformation update-stack \
--stack-name myapp-vpc-dev \
--template-body file://vpc-template.yaml \
--parameters [same as create]
aws cloudformation delete-stack --stack-name myapp-vpc-dev
Key CFN Features Used
Parameters:
- Make template reusable across environments
- Validation with AllowedPattern, AllowedValues
- Default values for convenience
Conditions:
IsProduction creates second NAT only in prod (cost savings)
!If function for conditional resource references
Mappings:
AZMapping for region-specific availability zones
- Makes template portable across regions
Intrinsic Functions:
!Ref - Reference parameters or resources
!GetAtt - Get attribute from resource
!Sub - String substitution
!Join - Concatenate values
!FindInMap - Lookup from mapping
Outputs with Exports:
- Export values for cross-stack references
- Other stacks can import with
!ImportValue
Terraform vs CloudFormation
| Feature | Terraform | CloudFormation |
|---|
| Multi-cloud | Yes | AWS only |
| State management | Required | Managed by AWS |
| Syntax | HCL (readable) | YAML/JSON (verbose) |
| Modules | Excellent | Nested stacks (complex) |
| Plan preview | terraform plan | Change sets |
| Drift detection | Built-in | Built-in |
Pro Tips
Use parameters for reusability
Export outputs for cross-stack references
Use conditions for environment differences
Always use DependsOn for correct ordering
Validate template before deploying
Common Conversions
Terraform → CloudFormation:
# Terraform
resource "aws_s3_bucket" "assets" {
bucket = "myapp-assets"
}
Resources:
AssetsBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: myapp-assets