Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for Terraform By James Joyner IV · · 11 min read

Designing Terraform Modules With AI as a Junior Engineer

AI can scaffold a Terraform module in seconds, but a good module is about interface design, not typing speed. Here is how to use AI without inheriting its bad defaults.

  • #terraform
  • #ai
  • #modules
  • #design

I used to think module design was the part of Terraform an AI could fully take over. You describe the resource, it spits out variables.tf, main.tf, outputs.tf, done. After shipping a few modules that way and then living with them for six months, I changed my mind. AI is fantastic at the typing part of a module and genuinely bad at the interface part — and the interface is the whole point of a module.

A module is an abstraction. Its value is in what it hides and what it exposes, not in how fast you generated the HCL. Treat the AI like a fast junior engineer: it produces a working first draft quickly, but a senior person decides what the module’s contract should be.

The trap: AI optimizes for “works”, not “lives well”

Ask a model to “write a Terraform module for an S3 bucket” and you will get something like this:

variable "bucket_name" {
  type = string
}

resource "aws_s3_bucket" "this" {
  bucket = var.bucket_name
}

It works. It’s also useless as a module, because it exposes one knob and bakes in zero opinions. No versioning, no encryption, no public-access block, no tags. The AI gave you the literal minimum that passes terraform plan. That is the default behavior you have to fight: models satisfy the prompt, not the production environment you never described.

Start with the interface, not the resources

Before you let the AI write any resource blocks, make it design the variables. I prompt it like this:

You are designing a reusable Terraform module for an S3 bucket used across a 40-team org. List the input variables you’d expose, with types, defaults, and a one-line rationale for each. Mark which ones are required. Do NOT write any resources yet.

Now you get a conversation about the contract:

variable "name" {
  description = "Bucket name; must be globally unique"
  type        = string
}

variable "versioning_enabled" {
  description = "Enable object versioning"
  type        = bool
  default     = true
}

variable "force_destroy" {
  description = "Allow deletion of non-empty bucket. Dangerous in prod."
  type        = bool
  default     = false
}

variable "tags" {
  type    = map(string)
  default = {}
}

This is where your judgment matters. The AI suggested force_destroy with a false default — good. It probably also suggested an acl variable, which you should reject because ACLs are deprecated in favor of bucket policies. You’re reviewing a junior’s PR, not accepting a delivery.

Pro Tip: Ask the model to justify every variable with a default of true. Permissive defaults are where modules quietly become insecure. If it can’t justify true, flip it to false and make the caller opt in.

Make it validate its own inputs

Once the interface is settled, AI is great at writing the boilerplate you’d never bother typing by hand — like validation blocks:

variable "environment" {
  type = string
  validation {
    condition     = contains(["dev", "staging", "prod"], var.environment)
    error_message = "environment must be dev, staging, or prod."
  }
}

Prompt: “Add validation blocks to these variables that catch the misconfigurations most likely to cause an outage.” This is high-value, low-risk AI work. The blast radius of a bad validation block is a failed plan, not a deleted bucket.

The encryption and access defaults it will forget

Every cloud resource has a set of “you should always do this” companions that the AI omits unless prompted. For an S3 bucket that’s the public access block, encryption config, and ownership controls. Don’t trust it to remember. Give it a checklist:

For this S3 module, add: a aws_s3_bucket_public_access_block with all four settings true, server-side encryption with bucket_key_enabled = true, and aws_s3_bucket_versioning. Wire each to the relevant variable.

resource "aws_s3_bucket_public_access_block" "this" {
  bucket                  = aws_s3_bucket.this.id
  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

The pattern: you supply the security baseline as an explicit requirement, the AI translates it into HCL. Never let the omission of a security control be the AI’s decision.

Outputs are an API too

Junior engineers under-export outputs, and so does the AI. It’ll give you bucket_id and stop. But callers also want the ARN (for IAM policies), the regional domain name (for CloudFront origins), and sometimes the full bucket object. Ask explicitly: “What outputs would a downstream module realistically need from this?” Then prune anything that leaks internal implementation you don’t want to commit to.

output "arn" {
  description = "Bucket ARN, for use in IAM policy documents"
  value       = aws_s3_bucket.this.arn
}

Outputs are a public contract. Once another team references module.logs.arn, you can’t rename it without breaking them. The AI doesn’t feel that pain, so you have to.

Never connect the model to real infrastructure

Throughout all of this, the AI only ever touches text in your editor. It does not run terraform apply. It does not hold AWS credentials. It does not get write access to your state backend. Module design is the safest place to use AI precisely because the output is reviewable HCL that goes through your normal plan-and-review pipeline. Keep it that way. If you find yourself wiring an agent to apply changes “to save time,” stop — that’s the line where a fast junior engineer becomes an unsupervised one with prod keys.

When you want a structured workflow for this kind of review-heavy collaboration, our prompt workspace and curated prompts library are built around exactly this loop. You can also run the resulting HCL through an automated pass in the code review dashboard before a human looks at it.

Conclusion

AI turns a two-hour module-writing session into twenty minutes — but only the typing collapses, not the thinking. Design the interface first, supply the security baseline as a hard requirement, reject permissive defaults, and treat every output as a contract you’ll have to honor. The model is a fast junior engineer: brilliant at boilerplate, oblivious to consequences. You stay the senior who signs off. For more on this category, see AI for Terraform, and grab a starting point from our prompt packs.

Free download · 368-page PDF

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.