You describe a VPC with three subnets, a security group, and an EC2 instance. Sixty seconds later, Claude Code has written the HCL, validated it, planned it, and is waiting for your go-ahead to apply. The files are declarative, validation is instant (terraform validate), and the plan/apply cycle gives you a review step before anything touches real infrastructure. You describe what you want, Claude Code writes the HCL, validates it, plans it, and waits for your approval before applying.
This guide is part of the Claude Code for DevOps Engineers series. Every demo ran against a real OpenStack cloud environment. The plan output, the error messages, the resource IDs are all from actual execution. If you need Terraform installed first, the Terraform installation guide for Linux covers that.
Current as of March 2026. Tested with OpenTofu 1.11.5, OpenStack provider 2.1.x, Rocky Linux 10 images
What You Need
- Claude Code installed and authenticated
- Terraform 1.2+ or OpenTofu 1.6+ installed
- Cloud credentials configured (AWS, GCP, Azure, or OpenStack)
- A non-production environment for testing (the demos create and destroy real resources)
Generate a Terraform Module from Scratch
The first demo shows the full cycle: describe what you need, Claude Code generates the HCL, validates, plans, and deploys. One conversation, real infrastructure at the end.
Write a Terraform config that creates:
1. A security group allowing SSH (22), HTTP (80), and HTTPS (443)
2. A compute instance using the Rocky Linux 10 image
3. Outputs for the instance IP and name
Use the OpenStack provider. Validate, then plan it.
Claude Code generates the HCL with proper resource references:
resource "openstack_networking_secgroup_v2" "web" {
name = "claude-demo-web"
description = "Allow SSH and HTTP access"
}
resource "openstack_networking_secgroup_rule_v2" "ssh" {
direction = "ingress"
ethertype = "IPv4"
protocol = "tcp"
port_range_min = 22
port_range_max = 22
remote_ip_prefix = "0.0.0.0/0"
security_group_id = openstack_networking_secgroup_v2.web.id
}
resource "openstack_networking_secgroup_rule_v2" "http" {
direction = "ingress"
ethertype = "IPv4"
protocol = "tcp"
port_range_min = 80
port_range_max = 80
remote_ip_prefix = "0.0.0.0/0"
security_group_id = openstack_networking_secgroup_v2.web.id
}
resource "openstack_compute_instance_v2" "web" {
name = "claude-demo-web"
image_id = "5454c0f2-706c-4237-83df-ecad6bfbbda8"
flavor_id = "10"
key_pair = "jkmutai-mac"
security_groups = [openstack_networking_secgroup_v2.web.name]
network {
uuid = "44a581e7-9dd8-4802-94da-810f59127558"
}
}
output "instance_ip" {
value = openstack_compute_instance_v2.web.access_ip_v4
}
output "instance_name" {
value = openstack_compute_instance_v2.web.name
}
Validation passes on the first try:
terraform validate
The output confirms no issues:
Success! The configuration is valid.
The plan shows exactly what Terraform will create:
terraform plan
Terraform shows every resource it will create with full attribute details:
OpenTofu will perform the following actions:
# openstack_compute_instance_v2.web will be created
+ resource "openstack_compute_instance_v2" "web" {
+ access_ip_v4 = (known after apply)
+ flavor_id = "10"
+ image_id = "5454c0f2-706c-4237-83df-ecad6bfbbda8"
+ key_pair = "jkmutai-mac"
+ name = "claude-demo-web"
+ security_groups = ["claude-demo-web"]
}
# openstack_networking_secgroup_v2.web will be created
+ resource "openstack_networking_secgroup_v2" "web" {
+ description = "Allow SSH and HTTP access"
+ name = "claude-demo-web"
}
# openstack_networking_secgroup_rule_v2.ssh will be created
# openstack_networking_secgroup_rule_v2.http will be created
# openstack_networking_secgroup_rule_v2.https will be created
Plan: 5 to add, 0 to change, 0 to destroy.
Five resources: one security group, three rules (SSH, HTTP, HTTPS), and one compute instance. The plan is clean. Claude Code waits for your confirmation before applying. If the plan shows unexpected destroys or changes, you catch them here, not in production.
After approval, terraform apply creates the resources. The security group and rules create in under 2 seconds. The instance takes 10-15 seconds depending on the cloud provider. Claude Code shows the apply output in real time and reports the final outputs (instance IP, name).
Debug a Broken Terraform Config
This is the highest-value section. Debugging Terraform errors is where most engineers spend their time, and it’s where Claude Code saves the most. Two real error scenarios from actual testing.
Error: “Reference to undeclared input variable”
A config references var.app_image_id but no variable block declares it:
resource "openstack_compute_instance_v2" "app" {
name = "app-server"
image_id = var.app_image_id
# ...
}
Running terraform validate produces:
Error: Reference to undeclared input variable
on main.tf line 3, in resource "openstack_compute_instance_v2" "app":
3: image_id = var.app_image_id
An input variable with the name "app_image_id" has not been declared.
This variable can be declared with a variable "app_image_id" {} block.
Tell Claude Code “terraform validate is failing, fix it” and it reads the error, creates the missing variable block with a description and default value, and re-validates. The fix takes less time than reading the error message yourself.
Error: “Reference to undeclared resource”
A security group reference uses the wrong resource name:
security_groups = [openstack_networking_secgroup_v2.app.name]
But the security group is defined as openstack_networking_secgroup_v2.web, not .app:
Error: Reference to undeclared resource
on main.tf line 6, in resource "openstack_compute_instance_v2" "app":
6: security_groups = [openstack_networking_secgroup_v2.app.name]
There is no managed resource "openstack_networking_secgroup_v2" "app"
definition in the root module.
Claude Code scans all .tf files in the project, finds the actual resource name (.web), and fixes the reference. It also checks for similar mismatches elsewhere in the config, catching errors you might miss if you only fixed the line Terraform reported.
Partial apply failures
Sometimes terraform apply creates some resources but fails on others. In our testing, the security group created successfully but the compute instance entered an ERROR state:
openstack_networking_secgroup_v2.web: Creation complete after 2s [id=dfa9f3b6-b311-...]
openstack_networking_secgroup_rule_v2.ssh: Creation complete after 1s
openstack_networking_secgroup_rule_v2.http: Creation complete after 1s
openstack_networking_secgroup_rule_v2.https: Creation complete after 0s
openstack_compute_instance_v2.web: Creating...
openstack_compute_instance_v2.web: Still creating... [10s elapsed]
Error: Error waiting for instance (d6034548-d35a-...) to become ready:
unexpected state 'ERROR', wanted target 'ACTIVE'
Claude Code diagnoses partial applies by checking terraform state list to see what was created, then investigating the failed resource. For an instance ERROR state, it checks cloud provider quotas, image availability, and network configuration. The fix depends on the root cause: quota exceeded, image deleted, or flavor unavailable in the selected availability zone.
The key takeaway: terraform state list after a failed apply tells you exactly what exists and what doesn’t. Claude Code runs this automatically when diagnosing apply failures.
Safety Guardrails for Terraform
Terraform can destroy infrastructure with a single command. Claude Code needs explicit rules to prevent accidental damage. Three layers of protection work together.
CLAUDE.md rules
Add these to your project’s CLAUDE.md:
# Terraform Safety Rules
- NEVER run `terraform apply` without `terraform plan` first
- NEVER run `terraform destroy` without explicit user confirmation
- NEVER run `terraform apply -auto-approve` in any environment
- NEVER modify state files directly (no `terraform state rm` or `state mv`)
- ALWAYS run `terraform validate` after editing any .tf file
- ALWAYS use `-out=tfplan` with plan, and apply from the saved plan file
- ALWAYS review the plan output before confirming apply
Permission rules in settings.json
Create .claude/settings.json in your Terraform project and commit it to Git:
{
"permissions": {
"allow": [
"Bash(terraform init*)",
"Bash(terraform validate*)",
"Bash(terraform plan*)",
"Bash(terraform fmt*)",
"Bash(terraform show*)",
"Bash(terraform output*)",
"Bash(terraform state list*)",
"Bash(terraform state show*)"
],
"deny": [
"Bash(terraform destroy*)",
"Bash(terraform apply -auto-approve*)",
"Bash(terraform state rm*)",
"Bash(terraform state mv*)",
"Bash(terraform state push*)"
]
}
}
Read-only operations run without prompting. Plan runs freely (it doesn’t change anything). Apply prompts for approval. Destroy and state manipulation are blocked entirely. Every team member who clones the repo inherits these rules automatically.
Validation hooks
Auto-validate every .tf file after Claude Code edits it:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "if echo \"$CLAUDE_FILE_PATHS\" | grep -q '\\.tf$'; then terraform validate 2>&1 || true; fi"
}
]
}
]
}
}
If validation fails, Claude Code sees the error immediately and fixes it before moving on. No broken configs accumulate. For the full configuration system, the .claude directory guide covers settings, hooks, and project-level rules in detail.
What Claude Code Gets Right (and Wrong) with Terraform
| Task | Quality | Notes |
|---|---|---|
| Single-resource configs | Excellent | Security groups, instances, S3 buckets, DNS records |
| Resource references | Excellent | Correct .id vs .name vs .arn usage |
| Provider-specific attributes | Good | Knows AWS, GCP, Azure, OpenStack well. Niche providers less so |
| Module composition | Good for simple, weak for complex | Single module: reliable. Cross-module references with conditionals: review carefully |
| State operations | Do not use | State manipulation needs human judgment. Block it in permissions |
| IAM policies | Needs review | Generates overly permissive policies. Always tighten manually |
| Variable validation | Good | Adds type constraints and descriptions, sometimes misses validation blocks |
| Backend configuration | Good | Knows S3, GCS, Azure blob, Consul patterns |
The consistent rule: always review the plan. Claude Code generates valid HCL in most cases, but “valid” and “correct for your environment” are different things. The plan is your safety net. Never skip it.
Practical Terraform Prompts
Be explicit about the cloud provider and resource types. “Create a VM” is vague. “Create an OpenStack compute instance with a security group allowing SSH and HTTP, using the Rocky Linux 10 image” gives Claude Code everything it needs.
Ask for outputs. Claude Code sometimes generates resources without outputs. Adding “include outputs for the instance IP and security group ID” ensures you get the values you’ll need for other modules or manual verification.
Request variables for environment-specific values. Hardcoded IPs, image IDs, and region names break when you move to a different environment. Asking “use variables for the image ID, flavor, and network” produces portable configs.
For debugging, paste the complete error. Terraform error messages include the file, line number, and resource name. Claude Code uses all of it. Saying “terraform plan failed” gives it nothing. Pasting the full error block gives it everything.
Ask Claude Code to explain the plan before applying. “What does this plan actually do? Are there any unexpected destroys or replacements?” makes Claude Code read the plan like a reviewer, flagging anything that looks wrong.
Part of the Claude Code for DevOps Series
This Terraform spoke connects to the broader series. The SSH server management guide covers configuring the servers Terraform provisions. The Ansible guide covers post-provisioning configuration management.
- Set Up Claude Code for DevOps Engineers (pillar with safety rules and permissions)
- Manage Servers with Claude Code via SSH
- Build and Debug Docker Containers with Claude Code
- Claude Code + Ansible: generate playbooks, multi-OS testing, debug SELinux
- Claude Code + Kubernetes: manifests, Helm charts, debugging CrashLoopBackOff
- Claude Code + GitHub Actions: automated PR review, Terraform validation
The Claude Code cheat sheet covers every command and shortcut for quick reference. For provisioning VMs on other platforms, the Terraform with KVM guide shows the same patterns with a different provider.