Terraform and OpenTofu infrastructure-as-code (IaC) — declare cloud/SaaS resources in HCL, manage state with remote backends and locking, author and consume modules, and run the init/plan/apply/destroy lifecycle. Use whenever the user mentions Terraform, OpenTofu, the tofu CLI, IaC, "infrastructure as code", provisioning cloud resources, .tf/HCL files, tfstate/backends, providers, modules, workspaces, drift, or importing existing infrastructure. For AWS-only IaC written in a general-purpose l...
Installs into .claude/skills of the current project.
Are you the author of Terraform?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/stanfish06-terraform)
---
name: terraform
description: Terraform and OpenTofu infrastructure-as-code (IaC) — declare cloud/SaaS resources in HCL, manage state with remote backends and locking, author and consume modules, and run the init/plan/apply/destroy lifecycle. Use whenever the user mentions Terraform, OpenTofu, the tofu CLI, IaC, "infrastructure as code", provisioning cloud resources, .tf/HCL files, tfstate/backends, providers, modules, workspaces, drift, or importing existing infrastructure. For AWS-only IaC written in a general-purpose language (TypeScript/Python) via CloudFormation, prefer aws-cdk-development instead.
license: MIT
metadata: {"version": "1.0", "skill-author": "vault-audit"}
---
# Terraform / OpenTofu
## Overview
Terraform is a declarative infrastructure-as-code tool: you describe desired resources in HCL (`.tf` files), and Terraform computes and applies the diff against real infrastructure via provider plugins. It works across AWS, GCP, Azure, Kubernetes, and hundreds of other APIs (Cloudflare, GitHub, Datadog, etc.).
**OpenTofu** is a drop-in open-source fork of Terraform under the Linux Foundation (MPL 2.0), created after HashiCorp relicensed Terraform to the BSL in 2023. It reads the same `.tf` files and state format; you just run `tofu` instead of `terraform`. Everything in this skill applies to both unless noted. OpenTofu has since shipped features Terraform lacks (notably client-side **state encryption**).
Use this skill when you need to provision, change, or tear down infrastructure reproducibly; manage `tfstate`; write reusable modules; detect drift; or import existing resources under management.
## Installation
Current stable as of mid-September 2026: **Terraform ~1.16.x** (1.17 in development) and **OpenTofu ~1.12.x**. HCL and the core CLI are stable across the 1.x line, so the workflow below is version-agnostic; check release notes for exact newest.
```bash
# macOS (Homebrew)
brew install terraform # or: brew install opentofu
# Linux — download the pinned release and put the single binary on PATH
# Terraform: https://developer.hashicorp.com/terraform/install
# OpenTofu: https://opentofu.org/docs/intro/install/ (has an install script + apt/dnf repos)
terraform version # or: tofu version
```
Pin the tool version per project with `required_version` (below) and commit `.terraform.lock.hcl` (the provider dependency lock) to Git so everyone resolves identical provider builds.
## Core workflow
### 1. Root config: terraform block, provider, resource, data source
```hcl
# main.tf
terraform {
required_version = ">= 1.9"
required_providers {
aws = {
source = "hashicorp/aws" # registry.terraform.io / registry.opentofu.org
version = "~> 5.0" # pessimistic constraint: >=5.0, <6.0
}
}
}
provider "aws" {
region = var.region
}
# A managed resource
resource "aws_s3_bucket" "assets" {
bucket = "acme-${var.env}-assets"
tags = local.common_tags
}
# A data source: read something Terraform does NOT manage
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"]
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-*"]
}
}
```
Reference attributes as `aws_s3_bucket.assets.arn` or `data.aws_ami.ubuntu.id`. Do not invent argument names — every provider's exact schema is in its registry docs (e.g. `registry.terraform.io/providers/hashicorp/aws/latest/docs`).
### 2. The lifecycle
```bash
terraform init # download providers + configure backend (run after any provider/backend change)
terraform init -upgrade # bump providers within version constraints, rewrite lock file
terraform fmt -recursive # canonical formatting
terraform validate # static/type checks (no API calls)
terraform plan -out=tfplan # compute diff; save it so apply matches exactly
terraform apply tfplan # apply the saved plan (no re-plan, no prompt)
terraform apply -auto-approve # plan + apply in one step (CI/dev only)
terraform destroy # tear down everything in state
terraform output # print root outputs (add -json for machine use)
```
### 3. Remote backend + state locking
State is Terraform's record of managed resources. Keep it in a shared, encrypted, **locked** backend — never on a laptop, never in Git.
```hcl
# S3 (native locking via a lock file, Terraform 1.10+ / OpenTofu 1.11+)
terraform {
backend "s3" {
bucket = "acme-tf-state"
key = "prod/network.tfstate"
region = "us-east-1"
encrypt = true
use_lockfile = true # replaces the old dynamodb_table lock; that arg is deprecated
}
}
```
```hcl
# GCS
backend "gcs" {
bucket = "acme-tf-state"
prefix = "prod/network"
}
```
```hcl
# Azure (azurerm) — uses blob leases for locking automatically
backend "azurerm" {
resource_group_name = "tfstate-rg"
storage_account_name = "acmetfstate"
container_name = "tfstate"
key = "prod.network.tfstate"
}
```
Backend blocks cannot use variables; supply per-environment values with `terraform init -backend-config=prod.s3.tfbackend`. State ops: `terraform state list`, `state show <addr>`, `state mv`, `state rm`, `terraform force-unlock <LOCK_ID>` (only if a lock is truly stale).
### 4. Variables, outputs, locals
```hcl
variable "env" {
type = string
description = "Deployment environment"
validation {
condition = contains(["dev", "staging", "prod"], var.env)
error_message = "env must be dev, staging, or prod."
}
}
variable "db_password" {
type = string
sensitive = true # redacted in CLI output (still stored in state — see gotchas)
}
locals {
common_tags = { Project = "acme", ManagedBy = "terraform" }
}
output "bucket_arn" {
value = aws_s3_bucket.assets.arn
}
```
Set variables via `-var`, a `*.tfvars` file (`-var-file=prod.tfvars`, and `terraform.tfvars`/`*.auto.tfvars` load automatically), or env vars `TF_VAR_env=prod`.
### 5. Modules — consume and author
```hcl
# Consume a registry module (pin the version!)
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.13.0"
name = "acme-prod"
cidr = "10.0.0.0/16"
}
# outputs: module.vpc.vpc_id
```
A module is just a directory of `.tf` files. Conventional layout: `main.tf`, `variables.tf`, `outputs.tf`, `versions.tf`. Reference a local one with `source = "./modules/network"` or a git module with `source = "git::https://github.com/org/repo.git//subdir?ref=v1.2.0"`. Consumers pass values to your `variable`s and read your `output`s. Publish to the public/private registry with the `terraform-<PROVIDER>-<NAME>` repo naming convention and semver git tags.
### 6. Workspaces (multiple states, one config)
```bash
terraform workspace new dev
terraform workspace select dev # each workspace = a separate state file
terraform workspace list
```
Reference the active one with `terraform.workspace`. CLI workspaces are best for small variations of the *same* config; for real environment isolation, prefer separate root directories/state keys (smaller blast radius, independent provider/backend config).
### 7. Drift detection
```bash
terraform plan -refresh-only # show out-of-band changes vs. state, propose nothing
terraform apply -refresh-only # reconcile state to reality without changing infra
```
### 8. Import existing infrastructure and refactor
```hcl
# import block (Terraform 1.5+): declarative, plannable, reviewable
import {
to = aws_s3_bucket.assets
id = "acme-prod-assets"
}
# then: terraform plan -generate-config-out=generated.tf (scaffolds the resource HCL)
# moved block: rename/move a resource in config WITHOUT destroy+recreate
moved {
from = aws_instance.web
to = aws_instance.frontend
}
# removed block (Terraform 1.7+): drop a resource from state but leave it alive
removed {
from = aws_s3_bucket.legacy
lifecycle { destroy = false }
}
```
The one-shot CLI equivalent of import is `terraform import aws_s3_bucket.assets acme-prod-assets`.
### 9. OpenTofu-specific: state encryption
OpenTofu can encrypt state and plan files client-side (Terraform cannot natively):
```hcl
terraform {
encryption {
key_provider "pbkdf2" "k" { passphrase = var.state_passphrase } # pass via TF_VAR_, never literal
method "aes_gcm" "m" { keys = key_provider.pbkdf2.k }
state { method = method.aes_gcm.m }
plan { method = method.aes_gcm.m }
}
}
```
## Gotchas / best practices
- **State holds secrets in plaintext.** DB passwords, private keys, and tokens land in `tfstate` even when a variable/output is marked `sensitive` (that only redacts CLI output). Use an encrypted, access-controlled remote backend (bucket policy/IAM), and OpenTofu state encryption if you can. Never commit `*.tfstate`.
- **Don't overuse `-target`.** It's a recovery escape hatch, not a workflow: it applies a partial graph, skips dependency resolution, and leaves state inconsistent. If you reach for it routinely, split the config into smaller root modules instead.
- **Never hand-edit state.** Use `terraform state mv/rm`, or `moved`/`import`/`removed` blocks. Manual JSON edits corrupt it silently.
- **Pin versions and commit the lock.** Pin `required_version`, use `~>` provider constraints, and commit `.terraform.lock.hcl` for reproducible builds across machines/CI.
- **Plan then apply the saved file in CI** (`plan -out` → `apply tfplan`) so what gets applied is exactly what was reviewed.
- **Protect critical resources**: `lifecycle { prevent_destroy = true }`; for zero-downtime replacement use `create_before_destroy = true`.
- **Secrets should flow through data sources / env vars** (Vault, AWS/GCP secret managers) rather than hardcoded literals — but remember anything read still enters state.
- **Drift happens** when people click in consoles. Run `plan -refresh-only` on a schedule to catch it early.
- **Keep blast radius small**: many small states with clear boundaries beat one giant monolith; share values via published outputs or the `terraform_remote_state` data source.
## Use this vs related skills
Use **terraform** for multi-cloud/provider-agnostic HCL IaC; use **aws-cdk-development** when the infrastructure is AWS-only and authored in a general-purpose language (TypeScript/Python) that synthesizes to CloudFormation.
## Resources
- Terraform docs: https://developer.hashicorp.com/terraform/docs — Registry (provider/module schemas): https://registry.terraform.io
- OpenTofu docs: https://opentofu.org/docs/ — Registry: https://registry.opentofu.org
- Language reference (blocks, functions, expressions): https://developer.hashicorp.com/terraform/language