Bas Franken
← All posts
Terraform Azure DevOps Infrastructure

I Built a Terraform Template Builder Because Engineers Kept Reinventing the Wheel

Every Azure workload we deployed followed roughly the same pattern. Resource group, VNet, subnets, Log Analytics, Application Insights, Key Vault, some application resources, NSGs, DNS zones, policies, tags. The building blocks were the same. The naming conventions were the same. The CIS compliance requirements were the same.

Yet every engineer spent hours assembling these pieces from scratch. Copy-pasting from previous deployments, adjusting naming conventions, forgetting a policy assignment, missing a tag. Then the review cycle would catch the mistakes, and they’d spend another hour fixing them.

So I built a tool that does it in minutes.

What It Does

The template builder is a 6-step wizard. You fill in your workload identity, pick your infrastructure patterns, configure your settings, and it generates complete Terraform .tf files ready to deploy. It feels like clicking together resources in the Azure portal, except at the end you get proper Infrastructure as Code instead of a portal deployment nobody can reproduce.

The key design decision: the actual Terraform generation is fully deterministic. No LLM involved in writing your infrastructure code. The AI is only used for suggestions and review, never for generating the Terraform itself. That’s intentional. You don’t want a language model deciding your subnet CIDRs.

The 6 Steps

Step 1: Identity. You define your organization code, project code, environment, and instance number. From these four inputs, the tool generates every resource name following Cloud Adoption Framework conventions. rg-hdn-sdcp-dev-001 for your resource group, vnet-hdn-sdcp-dev-001 for your VNet, and so on. You see a live naming preview as you type.

Step 2: Patterns & Compliance. You select the infrastructure patterns your workload needs. Web app, function app, Redis, SQL Server, container apps, storage accounts, API Management, VMs. Pick what you need. Then choose your CIS compliance level: Level 1 (secure defaults, enforced via preconditions) or Level 2 (premium SKUs, private endpoints, resource locks, purge protection).

Step 3: Infrastructure. The foundation layer. Azure region, VNet configuration (create new or reference existing), subnet prefixes, Log Analytics setup, DNS zones per service, hub VNet peering for cross-subscription connectivity. If you selected CIS Level 2, flow logs storage is automatically included.

Step 4: Configuration. This is where it gets dynamic. Based on the patterns you selected in step 2, the form generates the configuration fields you need. Each pattern has its own set of variables grouped by category: configuration, security, networking, identity, monitoring. Boolean toggles for CIS controls per building block. Environment-specific defaults are pre-populated.

Step 5: Policies & Tags. Azure Policy assignments for CIS Azure Foundations Benchmark v5.0.0 and Microsoft Cloud Security Benchmark. Enterprise tags are auto-populated from your identity inputs. You can add custom tags.

Step 6: Review. You see your complete configuration summary and generate the Terraform. The output is multiple .tf files: main.tf with your module orchestration, variables.tf with all input variables, outputs.tf, terraform.tf with provider configuration, plus policies.tf and roles.tf for governance. You can review and edit the generated code in a syntax-highlighted editor before committing.

The Stack

The frontend is React with TypeScript, Tailwind, and Radix UI. The code editor uses CodeMirror with HCL syntax highlighting so you can review and edit the generated Terraform directly in the browser.

The backend is Python with FastAPI. The template engine that generates Terraform is a deterministic renderer. No randomness, no LLM calls. Given the same inputs, you always get the same output. This is critical for infrastructure code. You need reproducibility.

The AI components run on LangGraph with Claude. There are two separate agent workflows: one for the intake pipeline (natural language to workload config, used in the chatbot mode) and one for the review chat (interactive code review after generation).

The AI Parts (And Where AI Is Deliberately Absent)

The LangGraph pipeline has 8 nodes: intake, check_completeness, ask_questions, normalize, template_generate, validate, output, and deploy.

Here’s what’s important: template_generate and validate are fully deterministic. No LLM. The template engine converts your form config into Terraform files using templates and pattern schemas. The validator runs terraform init and terraform validate on the generated output. These are the nodes where correctness matters most, and they don’t touch an LLM.

The AI is used where it adds value without risk:

The intake node parses natural language requests if someone uses the chatbot mode instead of the form. “I need a web app with a SQL database and Redis cache in West Europe” gets parsed into patterns and config.

The check_completeness node identifies missing required fields and generates clarifying questions. It knows which variables each pattern needs and asks for what’s missing.

The review chat is a separate LangGraph workflow that reviews your generated Terraform and suggests improvements. It can propose code changes that you apply or reject in the editor.

There’s also an AI copilot bar in the patterns step that suggests infrastructure patterns based on your workload description.

All AI inputs go through guardrails: prompt injection detection (18 regex patterns), max request length, HCL injection blocking. All AI outputs go through guardrails too: forbidden block detection (no raw resource blocks, no provisioner, no data "external"), secret pattern detection, module source validation.

CIS Compliance

The compliance model has three levels:

Level 1 is the default. Secure settings are baked into the building block modules. Terraform preconditions block non-compliant values at plan time. You can’t deploy a storage account with public blob access enabled because the precondition fails before apply.

Level 2 is opt-in. It enables premium SKUs, private endpoints, resource locks, and purge protection. More expensive, more secure.

CIS Overrides let you document exceptions. If you need public network access on a Key Vault for a specific reason, you can override the control. The override generates a warning in the plan output so there’s an audit trail.

This is the same approach I wrote about in enforcing CIS compliance at design time with AVM, but applied at the workload assembly level rather than the individual module level.

The Deploy Step

After review, the tool can push your generated Terraform files to an Azure DevOps repository. It creates a new branch, commits the files, and opens a pull request. The engineer reviews the PR, the pipeline runs terraform plan, and the team approves.

No manual file copying. No “I’ll commit it later.” The code goes straight from the builder into version control with a PR ready for review.

What I Learned Building This

The wizard UI was the easy part. Making the form dynamic based on pattern selection, generating naming previews, handling CIS level toggles. That’s standard frontend work. The hard part was the template engine. Getting the module wiring right across 15+ patterns with cross-dependencies (this subnet needs that NSG, this app needs that Key Vault reference) took far more iteration than expected.

Keeping AI out of the critical path was the right call. Early versions had the LLM generating Terraform directly. It produced plausible-looking code that sometimes had subtle issues: wrong resource references, missing dependencies, non-compliant defaults. Making the generation deterministic and reserving AI for suggestions and review eliminated an entire class of bugs.

The guardrails matter more than the AI. The input validation, output checking, and pre-validation logic is more code than the actual LLM integration. That ratio feels right for a world where most agentic AI still fails in production.

This is still a proof of concept. It’s not running in production yet. But even as a POC, it already demonstrates that the time engineers spend assembling workloads from scratch is time that can be eliminated with the right tooling. Whether this specific implementation makes it to production or evolves into something different, the pattern works. And honestly, building it taught me more about Terraform module design than any certification ever did.