# Terraform — Derived Rules > Derives from `domains/infrastructure-as-code/first-principles.md`. Applies P1–P10 to Terraform specifically. See also `opentofu.md` (the open-source fork), `state.md`, and `modules.md`. ## HCL Structure (P1 Declarative Intent) - Resources are declared, not scripted. A resource block states what should exist; Terraform reconciles it. - `resource "aws_s3_bucket" "logs" { ... }` — the type and name are the identity; the body is the desired state. - Data sources read existing state without claiming ownership: `data "aws_caller_identity" "current" {}`. - Variables are the input contract; outputs are the interface to consumers. Both are typed. ## Providers (P5 Version Everything, P7 Least Privilege) - Pin the provider version: `required_providers { aws = { source = "hashicorp/aws", version = "~> 5.0" } }`. - A provider block configures credentials and region. Credentials come from environment, files, or a secrets manager — never inline. - One credential set per environment. Do not reuse a prod credential in a dev workspace. ## The Core Workflow (P4 Plan Before Apply) - `terraform init` — resolve providers and modules. Reproducible from the lock file (`.terraform.lock.hcl`), which is committed. - `terraform plan` — preview the diff. Read it. Every line. The plan is the contract review. - `terraform apply` — execute the plan. Requires a reviewed plan in CI; in interactive use, requires typing `yes`. - `terraform destroy` — tear down. Treat `destroy` as a first-class operation with its own plan review; prod destroys are a change event, not a keystroke. ## Workspaces (P4 Locality of Environments) - Workspaces separate state for the same configuration across environments (dev, staging, prod). - Do not use workspaces to separate unrelated stacks — use separate configurations. A workspace is an environment axis, not a project axis. - State is isolated per workspace (see `state.md`). ## State Backends (P3 State is Truth, P8 Remote State with Locking) - Remote state is mandatory for any shared or production environment. See `state.md` for backend selection and locking. - Never commit `terraform.tfstate` to the repo. It is a secret-bearing artifact and a source of drift. - `terraform state` subcommands inspect and manipulate state directly — use sparingly, only for recovery. ## Registry and Modules (P6 Modules Compose) - The Terraform Registry hosts versioned, signed modules. Reference modules by version: `source = "terraform-aws-modules/vpc/aws"`, `version = "5.x"`. - Compose modules rather than copy-pasting blocks. A module is reviewed once and reused many times. - See `modules.md` for module structure, versioning, and the module-vs-copy boundary. ## Secrets (P10 Secrets Never in Code) - Secrets via provider data sources (`aws_secretsmanager_secret_version`), environment variables, or a dedicated secrets provider. Never a literal string in a resource block. - State may contain plaintext secrets if a resource attribute is sensitive. Mark attributes `sensitive = true` to keep them out of plan output; use a backend that encrypts state at rest (see `state.md`). This is the IaC angle on `domains/security/secrets.md` — secret hygiene is non-tradeable. ## What Violates Terraform Discipline | Violation | Principle | |-----------|-----------| | Unpinned provider (`source` without `version`) | P5 Version Everything | | `terraform apply` without a read `plan` | P4 Plan Before Apply | | Local state in a shared environment | P8 Remote State with Locking | | Hardcoded secret in HCL | P10 Secrets Never in Code | | Copy-pasted resource blocks instead of a module | P6 Modules Compose | | Manual change to a managed resource | P9 Drift is Recoverable | | Admin credentials in CI | P7 Least Privilege Providers |