Converting CloudFormation to Terraform With AI Without Trusting It Blindly
AI can translate CloudFormation YAML into HCL faster than any human, but the output lies in subtle ways. Here's a workflow that catches the lies before they ship.
- #terraform
- #ai
- #cloudformation
- #migration
- #aws
I inherited a few thousand lines of CloudFormation last year and a mandate to standardize everything on Terraform. Translating CloudFormation to HCL by hand is exactly the kind of mechanical, error-prone slog that makes you want to throw an AI at it — so I did. The first draft looked perfect. It was also subtly wrong in about a dozen places, in ways that would have caused real outages if I’d trusted it.
That’s the honest truth about AI for IaC translation: it’s a fast junior engineer who’s seen a million templates and will produce plausible HCL in seconds, and who will also confidently invent a resource argument that doesn’t exist. The workflow that works leans on its speed and never on its accuracy. It drafts; the toolchain and a human verify; and it never gets near your state or your AWS credentials — the import is the dangerous part, and that stays firmly in human hands.
Don’t migrate by recreating
The naive move is “AI, turn this YAML into HCL, then terraform apply.” That destroys and recreates every resource — new database, new bucket, data gone. The actual goal is to describe existing infrastructure in Terraform and adopt it without touching it. So the AI’s job is narrow: produce HCL whose arguments match what CloudFormation already created. The adoption happens through import, separately and carefully.
Start by dumping the real CloudFormation template, not someone’s stale copy in Git:
aws cloudformation get-template --stack-name prod-network \
--query 'TemplateBody' --output json > template.json
Let the AI draft the HCL skeleton
Hand the model the template and a tight instruction:
“Convert this CloudFormation resource into a Terraform
aws_*resource block. Match every property to its Terraform argument name. Where CloudFormation uses an intrinsic function like!Refor!GetAtt, leave a clearly marked TODO comment instead of guessing the Terraform reference. Do not invent arguments — if a CloudFormation property has no Terraform equivalent you’re sure of, flag it.”
The TODO instruction is the key. CloudFormation’s !Ref, !Sub, and !GetAtt don’t map one-to-one onto Terraform interpolation, and a model guessing those is where the subtle bugs come from. Forcing it to flag uncertainty turns silent errors into visible ones you can resolve.
A typical before/after:
# CloudFormation
Bucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub "${Env}-uploads"
VersioningConfiguration:
Status: Enabled
# Terraform draft
resource "aws_s3_bucket" "uploads" {
bucket = "${var.env}-uploads" # TODO: confirm !Sub mapping
}
resource "aws_s3_bucket_versioning" "uploads" {
bucket = aws_s3_bucket.uploads.id
versioning_configuration { status = "Enabled" }
}
Notice the model correctly split versioning into a separate resource — that’s the kind of provider-specific knowledge it’s good at. The !Sub mapping is flagged because it shouldn’t be trusted.
Verify structure before you trust meaning
The first gate is free and catches a lot:
terraform init -backend=false
terraform validate
terraform fmt -check
-backend=false means no state, no credentials — pure schema validation. If the AI hallucinated an argument like versioning = true (the old syntax), validate rejects it immediately. Fix, repeat, move on.
Pro Tip: Run tflint after validate. It catches deprecated arguments and provider-specific mistakes that validate waves through, which is precisely the category of error AI translation produces most.
Adopt, don’t apply
Now the careful part — and the part the AI never participates in. With the HCL drafted and validated, you import the real resources so Terraform manages them in place:
terraform import aws_s3_bucket.uploads prod-uploads
terraform plan
That terraform plan after import is the moment of truth. An empty plan means your HCL faithfully describes reality. A plan that wants to change anything means the AI’s translation diverged from the actual resource — wrong default, missed property, bad mapping. You loop: read the diff, correct the HCL, re-plan, until it’s empty.
Do this resource by resource, not stack by stack. Importing one bucket and confirming a clean plan is recoverable. Importing forty resources and getting a 200-line diff is a debugging nightmare. The AI accelerated the writing; the adopting stays slow and human on purpose.
Keep credentials away from the model
The architecture I settled on:
- The AI runs in a chat/workspace context with the template text pasted in. It sees YAML and writes HCL. It has no AWS access, no state, no
importability. - Every
aws cloudformation get-template,terraform import, andterraform planruns on a human’s authenticated session, reviewed step by step. - A clean post-import plan is the acceptance test for the AI’s work. No clean plan, no merge.
I keep the translation prompts reusable in the prompt library, and the Terraform prompt pack bundles a CloudFormation-to-HCL prompt that already includes the no-guessing and TODO conventions above. For the human review of the resulting PRs, the code review dashboard keeps the diff and sign-off together.
Handle the intrinsic functions deliberately
The single biggest source of subtle bugs is CloudFormation’s intrinsic functions, so give them their own pass rather than trusting the first draft. !Ref to a resource becomes a Terraform reference; !Ref to a parameter becomes a var; !GetAtt maps to a specific resource attribute that varies by type; !Sub becomes string interpolation. The mappings are mechanical but easy to get backwards. Walk them one at a time:
“This CloudFormation uses
!GetAtt MyBucket.Arn. In Terraform, what’s the equivalent reference for anaws_s3_bucketnameduploads? Give only the reference expression.”
For that example the answer is aws_s3_bucket.uploads.arn, but for other resource types the attribute name differs, and a model asked one reference at a time is far more accurate than one asked to translate a whole template’s worth of !GetAtts in a single shot. After resolving each TODO the model planted earlier, re-run terraform validate so a wrong attribute name surfaces immediately as a schema error rather than at import time.
Map the parameters and conditions explicitly
CloudFormation Parameters become Terraform variable blocks, and Conditions become count or for_each guards — translations the AI handles well but that change the config’s structure, so review them closely. A Condition: IsProd that gates a resource typically becomes:
resource "aws_cloudwatch_log_group" "audit" {
count = var.env == "prod" ? 1 : 0
name = "/audit/${var.env}"
}
That count introduces an index into the resource address, which matters for any downstream reference — exactly the kind of structural detail to confirm against a clean plan after import. Have the model list every parameter and condition it translated so you can check the set is complete before importing anything.
When the AI is wrong (it will be)
Common failure modes I’ve hit: it picks the wrong resource type for an ambiguous CloudFormation type, it collapses a nested property block into a flat argument, and it assumes default values that AWS doesn’t actually use. None of these survive a clean-plan check — which is exactly why the import-and-plan gate is non-negotiable. The model’s job is to get you to 90% in five minutes. The last 10% is yours.
Conclusion
AI turns a week of mechanical CloudFormation-to-Terraform translation into an afternoon of drafting plus review. The catch is that the draft is confidently, subtly wrong, so the entire workflow is built to surface those errors: validate for structure, tflint for provider mistakes, and an empty post-import plan as the final proof. The model writes fast and stays away from your credentials; you adopt slowly and keep the keys. Migrate that way and you get the speed without the outage. More migration guides live in the Terraform category.
Download the Free 500-Prompt DevOps AI Toolkit
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.