AI

Deploy Infrastructure with Claude Code and Terraform

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.

Original content from computingforgeeks.com - post 164932

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

TaskQualityNotes
Single-resource configsExcellentSecurity groups, instances, S3 buckets, DNS records
Resource referencesExcellentCorrect .id vs .name vs .arn usage
Provider-specific attributesGoodKnows AWS, GCP, Azure, OpenStack well. Niche providers less so
Module compositionGood for simple, weak for complexSingle module: reliable. Cross-module references with conditionals: review carefully
State operationsDo not useState manipulation needs human judgment. Block it in permissions
IAM policiesNeeds reviewGenerates overly permissive policies. Always tighten manually
Variable validationGoodAdds type constraints and descriptions, sometimes misses validation blocks
Backend configurationGoodKnows 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.

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.

Related Articles

Openstack How To Create SSH keypair on OpenStack using Terraform Ansible Top Infrastructure and Cloud Provisioning Automation Tools AI Deploy and Debug Kubernetes Apps with Claude Code AI Install Open WebUI with Ollama on Linux

Leave a Comment

Press ESC to close