CloudFormation template authoring is tedious by design: every resource type has dozens of properties, dependency ordering matters, and IAM policies require precise action names. AI tools vary significantly in their CloudFormation accuracy — some generate templates with deprecated properties or incorrect dependency chains. This guide compares Claude, GitHub Copilot, and the cfn-lint-integrated workflow.
What Good CloudFormation Generation Looks Like
Before comparing tools, establish what a good generated template requires:
- Correct
DependsOnor!Ref/!GetAttdependency expressions - IAM policies with least-privilege actions (not
*) DeletionPolicy: Retainon stateful resources (RDS, S3)- Parameters with
AllowedValuesandConstraintDescription - Outputs for cross-stack reference
- No deprecated properties (e.g., using
SubnetIdsinstead ofSubnetIdon ECS)
Test Scenario: ECS Fargate Service with ALB
All three tools were tested on the same prompt:
Generate a CloudFormation template for:
- ECS Fargate service running a Node.js container
- Application Load Balancer with HTTPS listener (ACM cert via parameter)
- ECS task role with read access to a specific SSM Parameter Store path
- CloudWatch log group with 30-day retention
- Security groups: ALB allows 443 from internet, ECS allows 3000 from ALB only
- Auto-scaling: 2-10 tasks based on CPU (70% target)
- Parameters: Environment, ImageTag, CertificateArn, VpcId, SubnetIds
Claude’s Output Quality
Claude (Opus) generates templates that pass cfn-lint with zero warnings on the first attempt, approximately 80% of the time. Key observations:
Claude handles dependency chains correctly:
Resources:
# ECS depends on log group existing first
TaskDefinition:
Type: AWS::ECS::TaskDefinition
DependsOn: LogGroup
Properties:
Family: !Sub "${AWS::StackName}-task"
NetworkMode: awsvpc
RequiresCompatibilities:
- FARGATE
Cpu: "256"
Memory: "512"
ExecutionRoleArn: !GetAtt ExecutionRole.Arn
TaskRoleArn: !GetAtt TaskRole.Arn
ContainerDefinitions:
- Name: app
Image: !Sub "${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/myapp:${ImageTag}"
PortMappings:
- ContainerPort: 3000
LogConfiguration:
LogDriver: awslogs
Options:
awslogs-group: !Ref LogGroup
awslogs-region: !Ref AWS::Region
awslogs-stream-prefix: ecs
Claude generates least-privilege IAM correctly:
TaskRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: ecs-tasks.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: SSMReadPolicy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- ssm:GetParameter
- ssm:GetParameters
- ssm:GetParametersByPath
Resource:
- !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/myapp/${Environment}/*"
Note the specific actions (GetParameter, not ssm:*) and the ARN scoped to the environment parameter path.
Auto-scaling configuration (correct target tracking):
ScalableTarget:
Type: AWS::ApplicationAutoScaling::ScalableTarget
DependsOn: ECSService
Properties:
MinCapacity: 2
MaxCapacity: 10
ResourceId: !Sub "service/${ECSCluster}/${ECSService.Name}"
ScalableDimension: ecs:service:DesiredCount
ServiceNamespace: ecs
ScalingPolicy:
Type: AWS::ApplicationAutoScaling::ScalingPolicy
Properties:
PolicyName: CPUTargetTracking
PolicyType: TargetTrackingScaling
ScalingTargetId: !Ref ScalableTarget
TargetTrackingScalingPolicyConfiguration:
PredefinedMetricSpecification:
PredefinedMetricType: ECSServiceAverageCPUUtilization
TargetValue: 70.0
ScaleInCooldown: 300
ScaleOutCooldown: 60
Claude correctly uses ECSService.Name (not ECSService) in the ResourceId — a common error that breaks auto-scaling setup.
GitHub Copilot
Copilot generates reasonable CloudFormation when editing inside an existing template file. In isolation (empty file), the quality drops.
Common Copilot issues:
- Uses deprecated
Clusterproperty format instead of current syntax - IAM policies often use
ecs-tasks.amazonaws.comwithout the correct assume role format for newer resource types - Sometimes generates
!Subexpressions with incorrect variable references (${ClusterName}when the resource isECSCluster) - Auto-scaling resources frequently have incorrect
ResourceIdformat
Copilot works well for:
# When you type "# Parameter for environment" and let Copilot complete:
Parameters:
Environment:
Type: String
AllowedValues:
- production
- staging
- development
Default: staging
ConstraintDescription: Must be production, staging, or development
Parameter blocks with AllowedValues are Copilot’s strongest area — the patterns are formulaic and it generates them consistently.
cfn-lint Integration Workflow
Regardless of which AI tool you use, add cfn-lint to your validation loop:
# Install
pip install cfn-lint
# Validate a template
cfn-lint template.yaml
# Validate with specific rules
cfn-lint template.yaml --include-checks W
# Output JSON for AI processing
cfn-lint template.yaml --format json > lint-results.json
Using Claude to fix lint errors:
import anthropic
import subprocess
import json
client = anthropic.Anthropic()
def fix_cfn_template(template: str) -> str:
"""Run cfn-lint and ask Claude to fix errors."""
# Write template to temp file
with open("/tmp/template.yaml", "w") as f:
f.write(template)
# Run cfn-lint
result = subprocess.run(
["cfn-lint", "/tmp/template.yaml", "--format", "json"],
capture_output=True, text=True
)
if result.returncode == 0:
return template # No errors
lint_errors = result.stdout
# Ask Claude to fix
response = client.messages.create(
model="claude-opus-4-6",
max_tokens=4096,
messages=[{
"role": "user",
"content": f"""
Fix these cfn-lint errors in the CloudFormation template:
Errors:
{lint_errors}
Template:
{template}
Return only the corrected YAML template with no other text.
"""
}]
)
return response.content[0].text
# Iterative fix loop
template = initial_template
for attempt in range(3):
template = fix_cfn_template(template)
# Check if clean
result = subprocess.run(["cfn-lint", "/tmp/template.yaml"], capture_output=True)
if result.returncode == 0:
print(f"Clean after {attempt + 1} iterations")
break
Outputs and Cross-Stack References
AI tools often skip Outputs — remind them explicitly:
Also generate Outputs for cross-stack reference:
- ECSClusterArn
- ServiceName
- ALBDNSName
- ALBArn (for creating additional listeners in other stacks)
- TaskRoleArn (for other services that need the same SSM access)
Claude’s output section:
Outputs:
ECSClusterArn:
Description: ARN of the ECS cluster
Value: !GetAtt ECSCluster.Arn
Export:
Name: !Sub "${AWS::StackName}-ECSClusterArn"
ServiceName:
Description: Name of the ECS service
Value: !GetAtt ECSService.Name
Export:
Name: !Sub "${AWS::StackName}-ServiceName"
ALBDNSName:
Description: DNS name of the Application Load Balancer
Value: !GetAtt ApplicationLoadBalancer.DNSName
Export:
Name: !Sub "${AWS::StackName}-ALBDNSName"
TaskRoleArn:
Description: ARN of the ECS task role
Value: !GetAtt TaskRole.Arn
Export:
Name: !Sub "${AWS::StackName}-TaskRoleArn"
Validation Pipeline in CI
# .github/workflows/cfn-validate.yml
name: CloudFormation Validation
on:
pull_request:
paths:
- 'infrastructure/**/*.yaml'
- 'infrastructure/**/*.yml'
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install cfn-lint
run: pip install cfn-lint
- name: Lint all templates
run: cfn-lint infrastructure/**/*.yaml
- name: Validate with AWS
run: |
for template in infrastructure/**/*.yaml; do
echo "Validating: $template"
aws cloudformation validate-template \
--template-body file://$template \
--region us-east-1
done
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
Verdict
Claude generates the most accurate CloudFormation on first pass. It handles dependency ordering, IAM least-privilege, and auto-scaling resource IDs correctly. Use it for initial template generation.
Copilot is useful for adding individual resources to existing templates and generating parameter blocks. Not reliable for complex multi-resource templates from scratch.
cfn-lint loop is essential regardless of which tool generates the template. Three iterations of Claude + cfn-lint fix produces clean templates for 95%+ of scenarios.
Related Articles
- Best AI Tools for Writing Helm Charts
- Best AI Tools for Writing Kubernetes Custom Resource
- Best AI Assistants for AWS CloudFormation Template
- Best AI Tools for Writing GitHub Actions Reusable Workflow
- Best AI Tools for Writing Unit Test Mocks 2026 Built by theluckystrike — More at zovo.one
| Tool | Template Generation | Resource Coverage | Drift Detection | Pricing |
|---|---|---|---|---|
| Claude | Full stack templates | 200+ AWS resource types | Can analyze drift reports | API-based (per token) |
| ChatGPT (GPT-4) | Complete templates with conditions | Broad AWS coverage | Explains drift implications | $20/month (Plus) |
| GitHub Copilot | Inline YAML/JSON completion | Common resources well-covered | No drift analysis | $10-39/user/month |
| Amazon CodeWhisperer | AWS-optimized suggestions | Native AWS resource support | Integrated with AWS tooling | Free tier available |
| Cursor | Context-aware template generation | Good with existing stacks | Can read stack outputs | $20/month (Pro) |
Frequently Asked Questions
Are free AI tools good enough for ai tools for writing cloudformation templates?
Free tiers work for basic tasks and evaluation, but paid plans typically offer higher rate limits, better models, and features needed for professional work. Start with free options to find what works for your workflow, then upgrade when you hit limitations.
How do I evaluate which tool fits my workflow?
Run a practical test: take a real task from your daily work and try it with 2-3 tools. Compare output quality, speed, and how naturally each tool fits your process. A week-long trial with actual work gives better signal than feature comparison charts.
Do these tools work offline?
Most AI-powered tools require an internet connection since they run models on remote servers. A few offer local model options with reduced capability. If offline access matters to you, check each tool’s documentation for local or self-hosted options.
How quickly do AI tool recommendations go out of date?
AI tools evolve rapidly, with major updates every few months. Feature comparisons from 6 months ago may already be outdated. Check the publication date on any review and verify current features directly on each tool’s website before purchasing.
Should I switch tools if something better comes out?
Switching costs are real: learning curves, workflow disruption, and data migration all take time. Only switch if the new tool solves a specific pain point you experience regularly. Marginal improvements rarely justify the transition overhead.