DEV Community

Vijaya Bollu
Vijaya Bollu

Posted on

# I Built a Local AI Terraform Generator and Tested It By Actually Deploying to AWS — Here Are the Results

The Idea

Every time I needed a new AWS resource, I'd spend 20 minutes reading Terraform docs just to get the syntax right for something I'd done before. I wanted to type plain English and get working HCL back. But I also didn't want to just generate code — I wanted to know if it actually deploys. So I tested every resource by running terraform apply against a real AWS account.


How It Works

You describe infrastructure in plain English. The tool sends it to a local Llama 3.2 model via Ollama, which returns four Terraform files. Those files get saved to a generated/ folder, ready for terraform init and terraform apply.

Plain English → Python → Ollama (local) → Parse HCL → main.tf + variables.tf + outputs.tf + tfvars.example
Enter fullscreen mode Exit fullscreen mode

The key piece is the prompt. Getting consistent, parseable HCL out of an LLM required a very specific structure:

prompt = f"""You are a Terraform/OpenTofu expert. Generate production-ready infrastructure code.

USER REQUEST:
{description}

PROVIDER: {provider}

CRITICAL:
- Generate ONLY valid Terraform/HCL code
- NO markdown formatting or code blocks
- Start each file with a comment showing the filename
- Separate files with: ### FILENAME ###

Format your response like this:

### main.tf ###
terraform {{
  required_version = ">= 1.0"
  required_providers {{
    {provider} = {{
      source  = "hashicorp/{provider}"
      version = "~> 5.0"
    }}
  }}
}}

[rest of main.tf code]

### variables.tf ###
[variables code]

### outputs.tf ###
[outputs code]

### terraform.tfvars.example ###
[example values]

Generate production-ready code now:"""
Enter fullscreen mode Exit fullscreen mode

The ### FILENAME ### markers are what make the response parseable. The script splits on ###, reads the filename, grabs everything after it until the next marker, and writes that to disk. There's also a fallback parser for when the model goes off-script and wraps things in code blocks anyway.


Test Results: 10 Resources Tested

Resource Generated Validated Deployed
EC2 instance
S3 bucket
IAM role
VPC + subnets
Security group
RDS instance
ALB
Lambda
ECS task ⚠️ needed fix
Complex module ⚠️ needed fix

8/10 deployed first try. 2/10 needed minor manual fixes.

The ECS task definition had an incorrect network_mode value for Fargate. The complex multi-resource module had a missing depends_on for the security group. Both were one-line fixes once terraform validate pointed at them.


What It's Good At vs Where It Struggles

Good at:

  • Standard single-resource configs (EC2, S3, IAM, RDS) — near-perfect every time
  • Wiring dependencies correctly between resources it knows well
  • Generating variables.tf with descriptions and sensible defaults
  • Adding tags and naming conventions without being asked

Struggles with:

  • Very new AWS resources where Llama's training data is thin
  • Complex modules with many interdependent resources — sometimes misses a depends_on
  • Provider version pinning — occasionally suggests a deprecated argument from an older AWS provider version
  • ECS/EKS specifics — these configs are dense and the model sometimes gets task definition fields wrong

Honest assessment: treat it like a junior engineer who's read all the Terraform docs but hasn't deployed much to production. Good first draft, always needs a review.


The Prompt Engineering That Made It Work

Three things made the difference between garbage output and deployable HCL:

1. Kill the markdown. LLMs love wrapping code in hcl blocks. That breaks the file parser completely. The explicit instruction NO markdown formatting or code blocks eliminated this.

2. Show the exact format in the prompt. Telling the model to use ### main.tf ### as a separator, and including the terraform {} block structure directly in the prompt, anchored the output format. Without this, every response looked slightly different.

3. Demand variables explicitly. Early versions hardcoded values like instance_type = "t3.micro" directly in main.tf. Adding Use variables for configurable values to the requirements section fixed this — now everything configurable lands in variables.tf with proper descriptions.


Why Local Matters for IaC Generation

Your Terraform descriptions contain your architecture. "Create a VPC with private subnets, an RDS cluster for our auth service, and an ECS task that pulls from our private ECR registry" — that's a roadmap of your production infrastructure. Sending that to a cloud API means it leaves your machine.

Running Ollama locally means the description, the generated code, and any sensitive context like account IDs or naming patterns stay on your machine. For anything touching production infrastructure, that's not optional.


Try It

GitHub: https://github.com/ThinkWithOps
Live deploy demo: https://youtu.be/nhhZqrCEhOA

git clone https://github.com/ThinkWithOps/ai-devops-projects
cd ai-devops-projects/05-ai-terraform-generator
pip install -r requirements.txt

# Generate code
python src/terraform_generator.py \
  --description "EC2 instance with S3 bucket for logs" \
  --provider aws

# Deploy it
cd generated/
cp terraform.tfvars.example terraform.tfvars
terraform init && terraform plan && terraform apply
Enter fullscreen mode Exit fullscreen mode

Project 5 in my AI+DevOps series — all tools run locally with Ollama, zero cloud API costs.


What's your current Terraform workflow? I'm curious whether people are using Copilot for this, manually writing it, or something else entirely — drop it in the comments.

Top comments (0)