# Terraform User Guide

Terraform is a tool for building, changing, and versioning infrastructure safely and efficiently. You describe your infrastructure in declarative configuration files, and Terraform figures out what API calls to make to reach that desired state — creating, updating, or destroying resources across cloud providers, SaaS platforms, and in-house systems.

This guide is the complete, source-based reference for using the Terraform command-line tool: installing it, writing configuration, running the core workflow, and using every subcommand and flag. It covers Terraform **core** — the CLI and graph engine. The actual cloud-specific logic (AWS, Azure, GCP, Kubernetes, and thousands more) lives in **providers**, which Terraform downloads automatically as plugins.

> [!NOTE]
> This guide documents Terraform `1.16.0-dev`. Terraform is licensed under the [Business Source License 1.1 (BUSL-1.1)](https://github.com/hashicorp/terraform/blob/main/LICENSE), not a traditional open-source license — review the license terms before redistributing or building a competing service.

---

## 1. Quick Start

If you already have Terraform installed, here is the entire core workflow in under a minute. Create a directory, add one file, and run four commands.

Create `main.tf`:

```hcl
terraform {
  required_providers {
    random = {
      source  = "hashicorp/random"
      version = "~> 3.0"
    }
  }
}

resource "random_pet" "name" {
  length = 2
}

output "pet_name" {
  value = random_pet.name.id
}
```

Then run:

```bash
terraform init      # download the random provider and set up the working directory
terraform plan      # preview what will be created
terraform apply     # create the resource (asks for confirmation)
terraform output    # print the generated name
terraform destroy   # tear it down again
```

Expected output from `terraform apply` (abbreviated):

```text
Terraform will perform the following actions:

  # random_pet.name will be created
  + resource "random_pet" "name" {
      + id     = (known after apply)
      + length = 2
      ...
    }

Plan: 1 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

random_pet.name: Creating...
random_pet.name: Creation complete after 0s [id=helpful-mongoose]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Outputs:

pet_name = "helpful-mongoose"
```

> [!TIP]
> The `random` provider creates no real cloud infrastructure and needs no credentials, which makes it perfect for trying the workflow before pointing Terraform at a real cloud account.

---

## 2. Installation

Terraform ships as a single, statically-linked binary named `terraform`. There is nothing to compile and no runtime to install — download the binary for your platform, put it on your `PATH`, and you are done.

=== "macOS (Homebrew)"

    ```bash
    brew tap hashicorp/tap
    brew install hashicorp/tap/terraform
    ```

=== "Windows (winget)"

    ```powershell
    winget install HashiCorp.Terraform
    ```

=== "Linux (Ubuntu/Debian)"

    ```bash
    wget -O- https://apt.releases.hashicorp.com/gpg | \
      sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
    echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] \
      https://apt.releases.hashicorp.com $(lsb_release -cs) main" | \
      sudo tee /etc/apt/sources.list.d/hashicorp.list
    sudo apt update && sudo apt install terraform
    ```

=== "Manual (any OS)"

    Download the appropriate zip from the [releases page](https://developer.hashicorp.com/terraform/install), unzip it, and move the `terraform` binary into a directory on your `PATH` (for example `/usr/local/bin`).

### Verify the installation

```bash
terraform version
```

```text
Terraform v1.16.0
on linux_amd64
```

To see machine-readable version information (useful in CI scripts):

```bash
terraform version -json
```

> [!IMPORTANT]
> Terraform builds are published for many platforms, including Linux on `s390x` (zLinux). If `terraform version` reports an older release than you expected, you likely have a second copy earlier on your `PATH`; run `which -a terraform` (or `where terraform` on Windows) to find it.

### Shell autocompletion

Terraform can install tab-completion for Bash and Zsh:

```bash
terraform -install-autocomplete
```

To remove it later:

```bash
terraform -uninstall-autocomplete
```

---

## 3. Core Concepts

You only need a handful of concepts to be productive. Each is expanded later in this guide.

**Configuration**
: The set of `.tf` files in a directory that describe your desired infrastructure, written in the HashiCorp Configuration Language (HCL). See [§5](#5-writing-configuration).

**Provider**
: A plugin that teaches Terraform how to talk to a specific platform's API (AWS, Azure, GCP, GitHub, Kubernetes, etc.). Providers are downloaded during `terraform init` from a registry. Terraform core itself contains no cloud-specific code.

**Resource**
: A single piece of infrastructure managed by Terraform — a virtual machine, a DNS record, a database. Declared with a `resource` block.

**State**
: Terraform's record of the real-world objects it manages, mapping each resource in your configuration to a real object's ID. Stored by default in a local `terraform.tfstate` file, or remotely via a *backend*.

**Plan**
: A preview of the actions (create, update, destroy, replace) Terraform would take to make reality match your configuration. Plans let you review changes before they happen.

**Apply**
: The step that actually executes a plan, calling provider APIs to create, change, or destroy resources.

**Backend**
: Where Terraform stores state and (optionally) runs operations. The default is `local` (a file on disk); remote backends like `s3`, `gcs`, or HCP Terraform enable team collaboration and state locking.

> [!NOTE]
> Terraform is **declarative**: you describe the end state you want, not the steps to get there. Terraform computes the steps by comparing your configuration, the prior state, and the real infrastructure.

---

## 4. The Core Workflow

Almost everything you do with Terraform follows the same loop: **write → init → plan → apply**. When you are finished with infrastructure, you **destroy** it.

```text
   ┌──────────┐     ┌────────┐     ┌────────┐     ┌─────────┐
   │  write   │ ──▶ │  init  │ ──▶ │  plan  │ ──▶ │  apply  │
   │  *.tf    │     │        │     │ review │     │ execute │
   └──────────┘     └────────┘     └────────┘     └─────────┘
                                        ▲              │
                                        └──── edit ◀───┘
```

### 4.1 `terraform init` — prepare the working directory

Run `init` once per working directory (and again whenever you add a provider or module, or change the backend). It downloads providers and modules, sets up the backend, and writes the dependency lock file `.terraform.lock.hcl`.

```bash
terraform init
```

```text
Initializing the backend...
Initializing provider plugins...
- Finding hashicorp/random versions matching "~> 3.0"...
- Installing hashicorp/random v3.6.0...
- Installed hashicorp/random v3.6.0 (signed by HashiCorp)

Terraform has been successfully initialized!
```

> [!TIP]
> `terraform init` is always safe to run again. It never destroys configuration or state. Commit the generated `.terraform.lock.hcl` to version control so your whole team installs identical provider versions.

### 4.2 `terraform plan` — preview changes

```bash
terraform plan
```

Terraform reads your configuration, refreshes the state against real infrastructure, and prints a diff. Symbols in the plan:

| Symbol | Meaning |
|--------|---------|
| `+` | resource will be **created** |
| `-` | resource will be **destroyed** |
| `~` | resource will be **updated in place** |
| `-/+` | resource will be **replaced** (destroy then create) |
| `<=` | data source will be **read** |

Save a plan to a file to guarantee `apply` does exactly what you reviewed:

```bash
terraform plan -out=tfplan
```

### 4.3 `terraform apply` — make it real

```bash
terraform apply
```

Without a saved plan, `apply` generates a fresh plan and prompts for confirmation. Apply a saved plan with no prompt:

```bash
terraform apply tfplan
```

Skip the interactive prompt (use with care, common in CI):

```bash
terraform apply -auto-approve
```

### 4.4 `terraform destroy` — tear it down

```bash
terraform destroy
```

`destroy` is a convenience alias for `terraform apply -destroy`. It plans to remove every object Terraform manages, then asks for confirmation.

> [!CAUTION]
> `terraform destroy` and `terraform apply -auto-approve` can delete real, production infrastructure with no undo. Always review the plan, and protect critical resources with `lifecycle { prevent_destroy = true }` (see [§5.7](#57-the-lifecycle-meta-argument)).

### 4.5 Global options

These options go **before** the subcommand and apply to every command:

| Option | Description |
|--------|-------------|
| `-chdir=DIR` | Switch to a different working directory before running the subcommand, e.g. `terraform -chdir=envs/prod apply`. |
| `-help` | Show top-level help, or help for a specified subcommand. |
| `-version` | Alias for the `version` subcommand. |

```bash
terraform -chdir=environments/production plan
```

---

## 5. Writing Configuration

Terraform configuration is written in **HCL** (HashiCorp Configuration Language) in files ending `.tf`. A JSON variant (`.tf.json`) exists for machine generation. A configuration is made up of **blocks** and **arguments**:

```hcl
block_type "label_one" "label_two" {
  argument = "value"

  nested_block {
    other = 123
  }
}
```

The following top-level block types are recognized by Terraform's configuration parser.

| Block | Purpose |
|-------|---------|
| `terraform` | Settings for Terraform itself: required version, required providers, backend, cloud. |
| `provider` | Configure a provider (region, credentials, endpoints). |
| `resource` | Declare a managed infrastructure object. |
| `data` | Read information from a provider without managing it. |
| `variable` | Declare an input variable. |
| `output` | Declare an output value. |
| `locals` | Define local (named) values for reuse within a module. |
| `module` | Call a child module. |
| `ephemeral` | Declare an ephemeral resource (value exists only during an operation, never stored in state). |
| `moved` | Record that a resource's address changed, to refactor without destroy/recreate. |
| `removed` | Record that a resource was removed from configuration but should be forgotten, not destroyed. |
| `import` | Declaratively import existing infrastructure into state. |
| `check` | Define an assertion that is validated during plan and apply. |
| `action` | Define an action (used with action triggers). |

### 5.1 The `terraform` settings block

```hcl
terraform {
  required_version = ">= 1.6.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }

  backend "s3" {
    bucket = "my-terraform-state"
    key    = "prod/terraform.tfstate"
    region = "us-east-1"
  }
}
```

- `required_version` — constrain which Terraform CLI versions may run this configuration.
- `required_providers` — declare each provider's `source` address and acceptable `version`.
- `backend` — choose where state is stored (see [§9](#9-state-and-backends)).
- `cloud` — connect to HCP Terraform / Terraform Enterprise instead of a backend.

### 5.2 Providers

A `provider` block configures a provider declared in `required_providers`:

```hcl
provider "aws" {
  region = "us-east-1"
}

# A second, aliased configuration of the same provider
provider "aws" {
  alias  = "west"
  region = "us-west-2"
}
```

Reference the aliased provider on a resource with the `provider` meta-argument:

```hcl
resource "aws_s3_bucket" "west_bucket" {
  provider = aws.west
  bucket   = "my-west-bucket"
}
```

### 5.3 Resources

A `resource` block declares an object Terraform should create and manage. The two labels are the **resource type** (defined by a provider) and a **local name**:

```hcl
resource "aws_instance" "web" {
  ami           = "ami-0abcdef1234567890"
  instance_type = "t3.micro"

  tags = {
    Name = "web-server"
  }
}
```

Refer to this resource's attributes elsewhere as `aws_instance.web.id`, `aws_instance.web.private_ip`, and so on.

### 5.4 Data sources

A `data` block reads existing information without managing it:

```hcl
data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"]

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
  }
}

resource "aws_instance" "web" {
  ami           = data.aws_ami.ubuntu.id   # use the looked-up AMI
  instance_type = "t3.micro"
}
```

### 5.5 Input variables

```hcl
variable "instance_count" {
  type        = number
  default     = 1
  description = "How many web servers to create"

  validation {
    condition     = var.instance_count > 0
    error_message = "instance_count must be at least 1."
  }
}
```

Reference variables as `var.instance_count`. Supported `type` constraints include `string`, `number`, `bool`, `list(...)`, `set(...)`, `map(...)`, `object({...})`, `tuple([...])`, and `any`.

Set variable values in several ways (highest precedence last):

| Method | Example |
|--------|---------|
| Default in the `variable` block | `default = 1` |
| `terraform.tfvars` / `*.auto.tfvars` (auto-loaded) | `instance_count = 3` |
| `-var-file=FILE` on the command line | `terraform apply -var-file=prod.tfvars` |
| `-var 'name=value'` on the command line | `terraform apply -var 'instance_count=5'` |
| `TF_VAR_name` environment variable | `export TF_VAR_instance_count=5` |
| Interactive prompt (if no value and no default) | Terraform asks at the prompt |

### 5.6 Outputs and local values

```hcl
output "instance_ip" {
  value       = aws_instance.web.public_ip
  description = "Public IP of the web server"
  sensitive   = false
}

locals {
  common_tags = {
    Project   = "demo"
    ManagedBy = "terraform"
  }
}
```

Reference locals as `local.common_tags`. Read outputs after apply with `terraform output` (see [§7.4](#74-reading-outputs)).

### 5.7 The `lifecycle` meta-argument

Every `resource` and `data` block accepts a `lifecycle` nested block:

```hcl
resource "aws_db_instance" "main" {
  # ...
  lifecycle {
    create_before_destroy = true
    prevent_destroy       = true
    ignore_changes        = [tags["LastModified"]]
    replace_triggered_by  = [aws_instance.web.id]

    precondition {
      condition     = var.instance_count <= 10
      error_message = "Too many instances requested."
    }
    postcondition {
      condition     = self.status == "available"
      error_message = "Database did not become available."
    }
  }
}
```

| Setting | Effect |
|---------|--------|
| `create_before_destroy` | Create the replacement before destroying the old object (avoids downtime). |
| `prevent_destroy` | Cause an error if a plan would destroy this resource. |
| `ignore_changes` | Ignore changes to the listed attributes when planning. |
| `replace_triggered_by` | Force replacement when a referenced object changes. |
| `precondition` / `postcondition` | Assert invariants before/after the resource is handled. |

### 5.8 Other resource meta-arguments

These arguments work on `resource`, `data`, and `module` blocks:

| Meta-argument | Purpose | Example |
|---------------|---------|---------|
| `count` | Create N copies | `count = 3` → `aws_instance.web[0]` … `[2]` |
| `for_each` | Create one per map/set entry | `for_each = toset(["a","b"])` → `...["a"]` |
| `depends_on` | Declare an explicit dependency | `depends_on = [aws_iam_role.r]` |
| `provider` | Choose an aliased provider | `provider = aws.west` |

Resources additionally support nested `provisioner` and `connection` blocks (see [§5.10](#510-provisioners)) and `action_trigger` blocks.

### 5.9 Expressions, functions, and dynamic blocks

HCL supports rich expressions: string templates (`"${var.name}-server"`), conditionals (`var.env == "prod" ? 3 : 1`), `for` expressions, and splat expressions.

```hcl
# for expression
locals {
  upper_names = [for n in var.names : upper(n)]
}

# dynamic block — generate repeated nested blocks from a collection
resource "aws_security_group" "web" {
  name = "web"

  dynamic "ingress" {
    for_each = var.allowed_ports
    content {
      from_port   = ingress.value
      to_port     = ingress.value
      protocol    = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
    }
  }
}
```

Terraform ships **116 built-in functions**. Call them like `max(5, 12, 9)`. See the [function reference in §13.2](#132-built-in-functions).

### 5.10 Provisioners

Provisioners run scripts on the local machine or a remote resource as a last resort when no provider feature fits. Terraform includes three built-in provisioners:

| Provisioner | Runs |
|-------------|------|
| `local-exec` | A command on the machine running Terraform |
| `remote-exec` | A command on the remote resource (over SSH or WinRM) |
| `file` | Copies a file or directory to the remote resource |

```hcl
resource "aws_instance" "web" {
  # ...
  provisioner "local-exec" {
    command = "echo ${self.public_ip} >> ips.txt"
  }
}
```

> [!WARNING]
> Provisioners are a last resort. They are not declarative, do not appear in plans, and can leave resources in an inconsistent state if they fail. Prefer provider-native features, user data, or configuration-management tools where possible.

### 5.11 Modules

A module is a reusable directory of `.tf` files. Call one with a `module` block:

```hcl
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "5.0.0"

  name = "my-vpc"
  cidr = "10.0.0.0/16"
}
```

Reference module outputs as `module.vpc.vpc_id`. The `source` can be a local path (`./modules/vpc`), a Terraform Registry address, a Git URL, an HTTP archive, or an S3/GCS object. See [§8](#8-working-with-modules).

---

## 6. Formatting and Validating Configuration

### 6.1 `terraform fmt` — canonical formatting

```bash
terraform fmt
```

Rewrites `.tf`, `.tfvars`, and `.tftest.hcl` files into HashiCorp's canonical style. Useful flags:

```bash
terraform fmt -check      # exit non-zero if any file is not formatted (CI gate)
terraform fmt -diff       # show what would change
terraform fmt -recursive  # also format subdirectories
```

### 6.2 `terraform validate` — check correctness

```bash
terraform validate
```

Checks that the configuration is syntactically valid and internally consistent, without contacting any remote services. It requires an initialized directory. To validate without configuring a backend:

```bash
terraform init -backend=false
terraform validate
```

Machine-readable output for editors and CI:

```bash
terraform validate -json
```

> [!TIP]
> `terraform fmt -check` and `terraform validate` make excellent pre-commit hooks and CI steps — they are fast and never touch real infrastructure.

---

## 7. Inspecting Infrastructure

### 7.1 `terraform show` — read state or a plan

```bash
terraform show                # show current state, human-readable
terraform show -json          # show current state as JSON
terraform show tfplan         # show a saved plan file
terraform show -json tfplan   # show a saved plan as JSON
```

### 7.2 `terraform console` — interactive expression evaluation

```bash
terraform console
```

```text
> max(1, 5, 3)
5
> aws_instance.web.public_ip
"203.0.113.10"
> [for s in var.subnets : cidrhost(s, 1)]
```

The console loads state but never modifies it. Use `-plan` to evaluate against a fresh plan's state (to see values that haven't been applied yet), and `-var` / `-var-file` to supply variables.

### 7.3 `terraform graph` — visualize dependencies

```bash
terraform graph | dot -Tsvg > graph.svg
```

Produces the dependency graph in DOT format. Choose a specific operation graph with `-type=TYPE` (`plan`, `plan-refresh-only`, `plan-destroy`, or `apply`), render from a saved plan with `-plan=tfplan`, and highlight cycles with `-draw-cycles`.

### 7.4 Reading outputs

```bash
terraform output                 # all outputs, human-readable
terraform output instance_ip     # a single output
terraform output -json           # all outputs as JSON
terraform output -raw instance_ip  # raw string, no quotes (good for scripts)
```

### 7.5 Listing modules and providers

```bash
terraform modules            # list declared modules and resolved versions
terraform modules -json
terraform providers          # tree of provider requirements across modules
terraform providers schema -json   # full JSON schema of all used providers
```

---

## 8. Working with Modules

### 8.1 Installing and updating modules

`terraform init` installs modules automatically. To install or refresh modules outside of init:

```bash
terraform get             # download modules referenced by the configuration
terraform get -update     # check for and install newer allowed versions
```

### 8.2 Module sources

The `source` argument accepts many forms, resolved by Terraform's module installer:

| Source kind | Example |
|-------------|---------|
| Local path | `source = "./modules/network"` |
| Terraform Registry | `source = "terraform-aws-modules/vpc/aws"` |
| Git | `source = "git::https://github.com/org/repo.git//subdir?ref=v1.2.0"` |
| HTTP archive | `source = "https://example.com/module.zip"` |
| S3 / GCS | `source = "s3::https://s3.amazonaws.com/bucket/module.zip"` |

> [!NOTE]
> Pin registry and Git modules to a specific `version` or `?ref=` tag. Unpinned modules can change underneath you and produce surprising plans.

### 8.3 Refactoring with `moved`

When you rename a resource or move it into a module, add a `moved` block so Terraform updates state instead of destroying and recreating:

```hcl
moved {
  from = aws_instance.web
  to   = module.servers.aws_instance.web
}
```

---

## 9. State and Backends

Terraform records the objects it manages in **state**. By default this is a local file `terraform.tfstate`. For teams, use a **remote backend** so state is shared, locked, and durable.

### 9.1 Built-in backends

Terraform core ships with these backends:

| Backend | Storage |
|---------|---------|
| `local` | A file on local disk (default) |
| `remote` | HCP Terraform / Terraform Enterprise |
| `cloud` | HCP Terraform (via the `cloud` block) |
| `s3` | Amazon S3 (with optional DynamoDB / S3-native locking) |
| `gcs` | Google Cloud Storage |
| `azurerm` | Azure Blob Storage |
| `oss` | Alibaba Cloud OSS |
| `cos` | Tencent Cloud COS |
| `oci` | Oracle Cloud Object Storage |
| `consul` | HashiCorp Consul |
| `kubernetes` | Kubernetes secrets |
| `pg` | PostgreSQL |
| `http` | A generic REST endpoint |
| `inmem` | In-memory (testing only) |

Configure a backend in the `terraform` block ([§5.1](#51-the-terraform-settings-block)). For deployment, security, and tuning details of each backend, see the **Admin Guide**.

### 9.2 Migrating backends

After changing the `backend` block, re-run init to migrate state:

```bash
terraform init -migrate-state    # move existing state to the new backend
terraform init -reconfigure      # ignore existing state, start fresh with the new config
```

### 9.3 State inspection and manipulation

The `terraform state` subcommands let you inspect and surgically edit state. **Every command that modifies state writes a timestamped backup first.**

```bash
terraform state list                       # list all tracked resource instances
terraform state list aws_instance.web      # filter by address
terraform state show aws_instance.web      # show one resource's attributes
terraform state show -json aws_instance.web
terraform state identities -json           # list resource identities (JSON)
```

Modify state (use with care):

```bash
terraform state mv aws_instance.web aws_instance.app   # rename / move
terraform state rm aws_instance.web                    # forget without destroying
terraform state replace-provider hashicorp/aws registry.example.com/acme/aws
```

Most modifying commands support `-dry-run` (preview), `-lock=false`, and `-lock-timeout=DURATION`.

### 9.4 Reading and writing remote state directly

```bash
terraform state pull > backup.tfstate      # download current state to stdout
terraform state push backup.tfstate        # overwrite remote state from a file
```

> [!CAUTION]
> `terraform state push` overwrites remote state. Terraform refuses to push an older serial or a different lineage unless you pass `-force`. Forcing the wrong file can corrupt state for everyone using that workspace.

### 9.5 Releasing a stuck lock

If a previous operation crashed and left the state locked:

```bash
terraform force-unlock LOCK_ID
```

Get `LOCK_ID` from the error message Terraform printed when it failed to acquire the lock.

> [!WARNING]
> Only force-unlock when you are certain no other Terraform process is running against that workspace. Unlocking during an active operation can corrupt state.

---

## 10. Workspaces

Workspaces let one configuration manage multiple distinct state files — for example separate `dev`, `staging`, and `prod` instances of the same infrastructure. The starting workspace is `default`.

```bash
terraform workspace list             # list workspaces (* marks current)
terraform workspace show             # print the current workspace name
terraform workspace new staging      # create and switch to a new workspace
terraform workspace select prod      # switch to an existing workspace
terraform workspace select -or-create dev   # switch, creating it if needed
terraform workspace delete staging   # delete a workspace
```

Reference the current workspace in configuration with `terraform.workspace`:

```hcl
resource "aws_instance" "web" {
  instance_type = terraform.workspace == "prod" ? "t3.large" : "t3.micro"
}
```

> [!NOTE]
> `terraform env` is a deprecated alias for `terraform workspace` (`env list`, `env select`, `env new`, `env delete`). Use `workspace` in new work.

---

## 11. Importing Existing Infrastructure

To bring an object that was created outside Terraform under management, you have two approaches.

### 11.1 Import blocks (declarative, recommended)

Add an `import` block and run a plan; Terraform plans to import the object into state:

```hcl
import {
  to = aws_instance.web
  id = "i-0abc123def456"
}
```

```bash
terraform plan -generate-config-out=generated.tf   # optionally generate matching HCL
terraform apply
```

### 11.2 The `import` command (one-off)

```bash
terraform import aws_instance.web i-0abc123def456
```

The `ADDR` is the resource address; the `ID` is provider-specific (look it up in the provider's documentation for that resource type). The command makes read-only API calls and updates state but does not change infrastructure. Pass `-config=path` to point at the configuration, and `-var`/`-var-file` if the provider needs variables.

---

## 12. Testing Configuration

Terraform has a built-in testing framework. Write `.tftest.hcl` files describing `run` blocks with assertions, then:

```bash
terraform test
```

```text
tests/main.tftest.hcl... in progress
  run "valid_string_concat"... pass
tests/main.tftest.hcl... tearing down
tests/main.tftest.hcl... pass

Success! 1 passed, 0 failed.
```

Useful flags:

| Flag | Purpose |
|------|---------|
| `-filter=testfile` | Run only the named test file(s); repeatable. |
| `-test-directory=path` | Look for tests in a directory other than `tests`. |
| `-verbose` | Print the plan or state for each run block. |
| `-json` | Machine-readable output. |
| `-junit-xml=path` | Write a JUnit XML report (for CI). |
| `-parallelism=n` | Concurrent operations per run (default 10). |
| `-cloud-run=source` | Execute the test run remotely on HCP Terraform / Enterprise. |

> [!CAUTION]
> `terraform test` creates **real** infrastructure and attempts to clean it up afterward. Watch the output to confirm teardown succeeded, and run tests against throwaway accounts.

---

## 13. Complete Command Reference

`terraform [global options] <subcommand> [args]`. Global options (`-chdir`, `-help`, `-version`) are documented in [§4.5](#45-global-options). Below is every subcommand with its options, grouped by purpose.

### 13.1 Main commands

#### `terraform init`

Initialize a working directory: create initial files, configure the backend, install modules and providers, write the lock file.

| Option | Description |
|--------|-------------|
| `-backend=false` | Skip backend/HCP Terraform initialization; reuse what was previously initialized. (alias `-cloud=false`) |
| `-backend-config=path` | Merge extra backend settings from an HCL file or `key=value`; repeatable. |
| `-force-copy` | Answer "yes" to all state-copy prompts during backend migration. |
| `-from-module=SOURCE` | Copy a module's contents into the directory before initializing. |
| `-get=false` | Do not download modules. |
| `-input=false` | Disable interactive prompts. |
| `-lock=false` | Do not lock state during backend migration. |
| `-lock-timeout=0s` | Duration to retry a state lock. |
| `-no-color` | Disable colored output. |
| `-json` | Machine-readable JSON output. |
| `-plugin-dir` | Directory of plugin binaries; overrides all default search paths and disables automatic installation; repeatable. |
| `-reconfigure` | Reconfigure the backend, ignoring saved configuration. |
| `-migrate-state` | Reconfigure the backend and migrate existing state. |
| `-upgrade` | Install the newest provider/module versions allowed by constraints, overriding the lock file. |
| `-lockfile=MODE` | Set lock-file mode; currently only `readonly` is valid. |
| `-ignore-remote-version` | Skip the local/remote version-compatibility check (HCP Terraform / remote backend). |
| `-test-directory=path` | Test directory, defaults to `tests`. |
| `-var 'foo=bar'`, `-var-file=filename` | Set input variables. |

#### `terraform validate`

Check that the configuration is valid and internally consistent without contacting remote services.

| Option | Description |
|--------|-------------|
| `-json` | Machine-readable JSON (always disables color). |
| `-no-color` | Disable colored output. |
| `-no-tests` | Do not validate test files. |
| `-test-directory=path` | Test directory, defaults to `tests`. |
| `-query` | Also validate `.tfquery.hcl` files. |
| `-var 'foo=bar'`, `-var-file=filename` | Set input variables. |

#### `terraform plan`

Generate a speculative execution plan without taking action.

| Option | Description |
|--------|-------------|
| `-destroy` | Plan to destroy all managed objects (destroy mode). |
| `-refresh-only` | Plan only to update state to match remote objects; propose no changes. |
| `-refresh=false` | Skip checking for external changes (faster, possibly stale). |
| `-replace=resource` | Force replacement of the given resource instance; repeatable. |
| `-target=resource` | Limit planning to the given object and its dependencies; repeatable. For exceptional use only. |
| `-var 'foo=bar'`, `-var-file=filename` | Set input variables. |
| `-compact-warnings` | Show warnings (without errors) in compact form. |
| `-detailed-exitcode` | Use detailed exit codes: `0`=no changes, `1`=error, `2`=changes present. |
| `-generate-config-out=path` | (Experimental) Generate HCL for resources referenced by `import` blocks into a new file. |
| `-input=true` | Prompt for variables not otherwise set. |
| `-lock=false` | Do not lock state. |
| `-lock-timeout=0s` | Duration to retry a state lock. |
| `-no-color` | Disable colored output. |
| `-out=path` | Write the plan to a file for use with `apply`. |
| `-parallelism=n` | Limit concurrent operations (default 10). |
| `-state=statefile` | Legacy local-backend option. |

#### `terraform apply`

Create or update infrastructure. Accepts an optional saved `PLAN` argument.

| Option | Description |
|--------|-------------|
| `-auto-approve` | Skip the interactive approval prompt. |
| `-backup=path` | Path to back up the existing state file; `-` disables backup. |
| `-compact-warnings` | Compact warning display. |
| `-destroy` | Destroy managed infrastructure (`terraform destroy` aliases this). |
| `-lock=false` | Do not lock state. |
| `-lock-timeout=0s` | Duration to retry a state lock. |
| `-input=true` | Prompt for variables not otherwise set. |
| `-no-color` | Disable colored output. |
| `-parallelism=n` | Limit parallel resource operations (default 10). |
| `-replace=resource` | Plan to replace this resource instance. |
| `-state=path`, `-state-out=path` | Legacy local-backend state paths. |
| `-var 'foo=bar'`, `-var-file=filename` | Set input variables. |

> [!NOTE]
> When you run `apply` **without** a saved plan, it also accepts all of the plan-customization options above (`-target`, `-refresh-only`, etc.). With a saved plan, those are read from the plan file.

#### `terraform destroy`

Convenience alias for `terraform apply -destroy`. Accepts the same plan-customization options as `plan`.

```bash
terraform destroy -target=aws_instance.web   # destroy a single resource and its dependents
```

### 13.2 Configuration & inspection commands

#### `terraform fmt`

`terraform fmt [options] [target...]` — rewrite configuration files to canonical format. JSON files are never modified. A target of `-` reads from STDIN.

| Option | Description |
|--------|-------------|
| `-list=false` | Don't list files whose formatting differs. |
| `-write=false` | Don't write changes back to files. |
| `-diff` | Show formatting diffs. |
| `-check` | Exit non-zero if any input is not formatted (no writes). |
| `-recursive` | Also process subdirectories. |
| `-no-color` | Disable colored output. |

#### `terraform console`

`terraform console [options]` — interactive expression console (read-only).

| Option | Description |
|--------|-------------|
| `-plan` | Evaluate against a freshly-planned state instead of current state. |
| `-state=path` | Legacy local-backend state path. |
| `-var 'foo=bar'`, `-var-file=foo` | Set variables. |

#### `terraform output`

`terraform output [options] [NAME]` — print output values.

| Option | Description |
|--------|-------------|
| `-json` | JSON output. |
| `-raw` | Print the raw string of a single output (no quoting). |
| `-no-color` | Disable colored output. |
| `-state=path` | Legacy local-backend state path. |

#### `terraform show`

`terraform show [options] [path]` — render a state or plan file.

| Option | Description |
|--------|-------------|
| `-json` | Machine-readable output. |
| `-no-color` | Disable colored output. |

#### `terraform graph`

`terraform graph [options]` — emit the dependency graph in DOT format.

| Option | Description |
|--------|-------------|
| `-type=TYPE` | Operation graph: `plan`, `plan-refresh-only`, `plan-destroy`, or `apply`. |
| `-plan=tfplan` | Use a saved plan file (implies `-type=apply`). |
| `-draw-cycles` | Highlight cycles with colored edges. |
| `-module-depth=n` | (Deprecated) Depth of modules to show. |
| `-var 'foo=bar'`, `-var-file=filename` | Set variables. |

#### `terraform graph` companions: `get`, `modules`, `providers`

```text
terraform get [-update] [-no-color] [-test-directory=path] [-var ...] [-var-file ...]
terraform modules [-json] [-var ...] [-var-file ...]
terraform providers [-test-directory=path] [-var ...] [-var-file ...]
terraform providers schema -json [-var ...] [-var-file ...]
terraform providers lock [options] [providers...]
terraform providers mirror [options] <target-dir>
```

`providers lock` updates the dependency lock file from the origin registry. Key options: `-fs-mirror=dir`, `-net-mirror=url`, `-platform=os_arch` (repeatable), `-enable-plugin-cache`, plus `-test-directory`/`-var`/`-var-file`.

`providers mirror` copies the needed provider plugins into a local directory for use as a filesystem or network mirror. Key options: `-platform=os_arch` (repeatable), `-lock-file=false`, plus `-var`/`-var-file`.

### 13.3 Resource & state lifecycle commands

#### `terraform refresh`

`terraform refresh [options]` — update state metadata to match real resources. (Equivalent to `terraform apply -refresh-only -auto-approve`.) Options include `-target`, `-input`, `-lock`, `-lock-timeout`, `-parallelism`, `-compact-warnings`, `-no-color`, `-var`, `-var-file`, and the legacy `-state`/`-state-out`/`-backup`.

#### `terraform taint` / `terraform untaint`

```bash
terraform taint <address>     # mark a resource for replacement on next apply
terraform untaint <address>   # remove the taint mark
```

Both accept `-allow-missing`, `-lock=false`, `-lock-timeout=0s`, `-ignore-remote-version`, `-var`/`-var-file`, and legacy `-state`/`-state-out`/`-backup`.

> [!TIP]
> In modern Terraform, prefer `terraform apply -replace=ADDRESS` over `taint` — it shows the replacement in the plan before you approve it.

#### `terraform state <subcommand>`

| Subcommand | Purpose |
|------------|---------|
| `state list [address...]` | List resource instances. Options: `-state`, `-id`. |
| `state show ADDRESS` | Show a resource's attributes. Options: `-state`, `-json`. |
| `state identities -json [address...]` | List resource identities. Options: `-state`, `-id`. |
| `state mv SOURCE DESTINATION` | Move/rename an item (optionally across state files). Options: `-dry-run`, `-lock`, `-lock-timeout`, `-ignore-remote-version`, `-var`/`-var-file`, legacy `-state`/`-state-out`/`-backup`. |
| `state rm ADDRESS...` | Remove items from state without destroying them. Options: `-dry-run`, `-backup`, `-lock`, `-lock-timeout`, `-state`, `-ignore-remote-version`, `-var`/`-var-file`. |
| `state pull` | Output current state to stdout. Options: `-var`/`-var-file`. |
| `state push PATH` | Overwrite remote state from a file (`-` reads stdin). Options: `-force`, `-lock`, `-lock-timeout`, `-var`/`-var-file`. |
| `state replace-provider FROM TO` | Replace the provider for resources in state. Options: `-auto-approve`, `-lock`, `-lock-timeout`, `-ignore-remote-version`, `-var`/`-var-file`, legacy `-state`/`-state-out`/`-backup`. |

#### `terraform force-unlock LOCK_ID`

Manually remove a state lock. Option: `-force` (skip the confirmation prompt).

#### `terraform import [options] ADDR ID`

Import existing infrastructure into state. Options: `-config=path`, `-input=false`, `-lock=false`, `-lock-timeout=0s`, `-no-color`, `-var`/`-var-file`, `-ignore-remote-version`, and legacy `-state`/`-state-out`/`-backup`.

### 13.4 Workspace commands

```text
terraform workspace list           [-json]
terraform workspace show
terraform workspace new NAME        [-lock=false] [-lock-timeout=0s] [-state=path]
terraform workspace select NAME     [-or-create=false]
terraform workspace delete NAME     [-force] [-lock=false] [-lock-timeout=0s]
```

### 13.5 Authentication, metadata & misc

| Command | Purpose |
|---------|---------|
| `terraform login [hostname]` | Obtain and store an API token for a host (default `app.terraform.io`). |
| `terraform logout [hostname]` | Remove locally-stored credentials for a host (token remains valid server-side until revoked). |
| `terraform metadata functions -json` | Print JSON signatures of all built-in functions. |
| `terraform version [-json]` | Show Terraform and plugin versions. |
| `terraform query [options]` | Query remote infrastructure using `.tfquery.hcl` list blocks. Options: `-generate-config-out=path`, `-json`, `-no-color`, `-var`/`-var-file`. |
| `terraform test [options]` | Run `.tftest.hcl` tests (see [§12](#12-testing-configuration)). |

> [!NOTE]
> A few commands are advanced, experimental, or legacy: `terraform stacks` (manage HCP Terraform stack operations), `terraform rpcapi` (an internal gRPC server for HCP Terraform — not for direct use), and the experimental `terraform cloud`, `terraform test cleanup`, and `terraform state migrate` (available only in alpha builds with experiments enabled). `terraform push` is removed — it served the end-of-life Terraform Enterprise v1.

### 13.6 The 116 built-in functions

Call functions inside expressions, e.g. `length(var.list)`. The complete set, by category:

**Numeric:** `abs`, `ceil`, `floor`, `log`, `max`, `min`, `parseint`, `pow`, `signum`

**String:** `chomp`, `endswith`, `format`, `formatlist`, `indent`, `join`, `lower`, `regex`, `regexall`, `replace`, `split`, `startswith`, `strcontains`, `strrev`, `substr`, `title`, `trim`, `trimprefix`, `trimspace`, `trimsuffix`, `upper`, `urlencode`

**Collection:** `alltrue`, `anytrue`, `chunklist`, `coalesce`, `coalescelist`, `compact`, `concat`, `contains`, `distinct`, `element`, `flatten`, `index`, `keys`, `length`, `lookup`, `matchkeys`, `merge`, `one`, `range`, `reverse`, `setintersection`, `setproduct`, `setsubtract`, `setunion`, `slice`, `sort`, `sum`, `transpose`, `values`, `zipmap`

**Encoding:** `base64decode`, `base64encode`, `base64gzip`, `csvdecode`, `jsondecode`, `jsonencode`, `textdecodebase64`, `textencodebase64`, `urlencode`, `yamldecode`, `yamlencode`

**Filesystem:** `abspath`, `dirname`, `basename`, `file`, `fileexists`, `fileset`, `filebase64`, `pathexpand`

**Date/Time:** `formatdate`, `timeadd`, `timecmp`, `timestamp`

**Hash/Crypto:** `base64sha256`, `base64sha512`, `bcrypt`, `filebase64sha256`, `filebase64sha512`, `filemd5`, `filesha1`, `filesha256`, `filesha512`, `md5`, `rsadecrypt`, `sha1`, `sha256`, `sha512`, `uuid`, `uuidv5`

**IP network:** `cidrhost`, `cidrnetmask`, `cidrsubnet`, `cidrsubnets`

**Type conversion:** `can` *, `tobool`, `tolist`, `tomap`, `tonumber`, `toset`, `tostring`, `try` *, `type` *, `nonsensitive`, `sensitive`, `issensitive`, `ephemeralasnull`

(`*` `can`, `try`, and `type` are special evaluation forms provided by the language alongside the registry.)

Get exact signatures at any time:

```bash
terraform metadata functions -json | jq '.function_signatures | keys'
```

---

## 14. Environment Variables

Terraform reads these environment variables. Set them to change behavior without editing configuration or passing flags.

### 14.1 Workflow

| Variable | Effect |
|----------|--------|
| `TF_CLI_ARGS` | Extra arguments prepended to every command. |
| `TF_CLI_ARGS_name` | Extra arguments for a specific subcommand, e.g. `TF_CLI_ARGS_plan`. |
| `TF_VAR_name` | Set input variable `name`. |
| `TF_DATA_DIR` | Directory for Terraform's working data (default `.terraform`). |
| `TF_IN_AUTOMATION` | If non-empty, suppress messages that assume an interactive prompt. |
| `TF_WORKSPACE` | Select a workspace without `terraform workspace select`. |

### 14.2 Logging & debugging

| Variable | Effect |
|----------|--------|
| `TF_LOG` | Enable logs at a level: `TRACE`, `DEBUG`, `INFO`, `WARN`, `ERROR`. |
| `TF_LOG_PATH` | Write logs to a file. |
| `TF_WARN_OUTPUT_ERRORS` | Warn (instead of error) on certain output evaluation problems. |

### 14.3 HCP Terraform & remote backend

| Variable | Effect |
|----------|--------|
| `TF_CLOUD_HOSTNAME` | HCP Terraform / Enterprise hostname. |
| `TF_CLOUD_ORGANIZATION` | Organization name. |
| `TF_CLOUD_PROJECT` | Project name. |
| `TF_FORCE_LOCAL_BACKEND` | Force local operations even with a `cloud` block. |

### 14.4 Provider development & advanced

| Variable | Effect |
|----------|--------|
| `TF_REATTACH_PROVIDERS` | Attach to providers already running (used by provider acceptance test frameworks). |
| `TF_CLOUD_PLUGIN_DEV_OVERRIDE` | Override the cloud plugin path during development. |
| `TF_DISABLE_PLUGIN_TLS` | Disable TLS for plugin connections (development only). |
| `TF_STACKS_HOSTNAME`, `TF_STACKS_TOKEN`, `TF_STACKS_ORGANIZATION_NAME`, `TF_STACKS_PROJECT_NAME`, `TF_STACKS_STACK_NAME`, `TF_STACKS_PLUGIN_DEV_OVERRIDE` | Configure the experimental `stacks` workflow. |

> [!WARNING]
> `TF_DISABLE_PLUGIN_TLS` and the `*_DEV_OVERRIDE` variables disable security checks and are intended only for plugin development. Never set them in production or CI that touches real infrastructure.

---

## 15. Troubleshooting

Format: each entry gives the error you see, its cause, the fix, and how to avoid it.

### Error: `Error: Inconsistent dependency lock file`

**Cause:** The configuration requires a provider (or version) not recorded in `.terraform.lock.hcl`, often after editing `required_providers`.
**Solution:** Run `terraform init -upgrade` to update the lock file to the newest allowed versions, or `terraform init` to install exactly what is needed.
**Prevention:** Commit `.terraform.lock.hcl` and run `init` after any change to provider requirements.

### Error: `Error: Provider configuration not present`

**Cause:** A resource references an aliased provider (`provider = aws.west`) that has no matching `provider "aws" { alias = "west" }` block, or a provider was removed while resources still depend on it.
**Solution:** Add the missing `provider` block, or use `terraform state replace-provider` if the provider's source address changed.
**Prevention:** Keep `provider` blocks and `required_providers` in sync with the resources that use them.

### Error: `Error acquiring the state lock`

**Cause:** Another Terraform run holds the lock, or a previous run crashed without releasing it.
**Solution:** Wait for the other run to finish. If you are certain none is running, copy the `ID` from the message and run `terraform force-unlock LOCK_ID`.
**Prevention:** Serialize Terraform runs per workspace (a single CI pipeline), and use `-lock-timeout` to wait rather than fail immediately.

### Error: `Error: Resource already managed by Terraform`

**Cause:** An `import` block or `terraform import` targets an address that already exists in state.
**Solution:** Remove the `import` block (the resource is already managed) or choose a different `to` address.
**Prevention:** Run `terraform state list` before importing.

### Error: `Instance cannot be destroyed` / `lifecycle.prevent_destroy`

**Cause:** A plan would destroy a resource that has `lifecycle { prevent_destroy = true }`.
**Solution:** Intentional protection — if the destroy is truly desired, remove or set `prevent_destroy = false`, then re-plan.
**Prevention:** Use `prevent_destroy` on stateful resources (databases, buckets) you must never lose.

### Error: `Error: Unsupported argument` / `Unsupported block type`

**Cause:** A typo, or an argument that does not exist for that resource/provider version.
**Solution:** Run `terraform validate`; check the provider's schema with `terraform providers schema -json`; confirm you are on the expected provider version.
**Prevention:** Pin provider versions and run `terraform fmt` + `terraform validate` before committing.

### Error: `Error: Cycle: ...`

**Cause:** Two or more resources depend on each other, directly or via `depends_on`, forming a loop Terraform cannot order.
**Solution:** Run `terraform graph -draw-cycles -type=plan | dot -Tsvg > cycle.svg` to visualize the loop, then break it (often by removing an unnecessary `depends_on` or splitting a resource).
**Prevention:** Rely on implicit dependencies (attribute references) instead of `depends_on` where possible.

### Error: `Saved plan is stale`

**Cause:** A saved plan file passed to `apply` no longer matches the current state (someone else applied changes in between).
**Solution:** Re-run `terraform plan -out=tfplan` and apply the fresh plan.
**Prevention:** Apply saved plans promptly; in teams, lock the workspace for the plan→apply window.

### Error: `Provider produced inconsistent result after apply`

**Cause:** A provider bug — the value applied differs from what was planned.
**Solution:** Upgrade the provider; if it persists, report it to the provider's repository. As a workaround, `ignore_changes` may avoid the churn.
**Prevention:** Keep providers current and pin to known-good versions.

### Error: `No valid credential sources found` (provider auth)

**Cause:** The provider could not find credentials (e.g. AWS keys, GCP service account).
**Solution:** Configure credentials per the provider's documentation (environment variables, shared config files, or arguments in the `provider` block).
**Prevention:** Use a consistent credential mechanism across your team and CI; never hard-code secrets in `.tf` files.

### Error: `terraform: command not found`

**Cause:** The binary is not on your `PATH`.
**Solution:** Move `terraform` into a `PATH` directory, or add its location to `PATH`. Verify with `terraform version`.
**Prevention:** Install via a package manager ([§2](#2-installation)) so `PATH` is handled for you.

### Symptom: a plan shows changes you didn't make ("drift")

**Cause:** Something changed the infrastructure outside Terraform, or a provider reports computed values differently.
**Solution:** Run `terraform plan -refresh-only` to review drift, then either apply it (accept reality) or correct the infrastructure.
**Prevention:** Manage resources exclusively through Terraform; use `ignore_changes` for fields modified by other systems.

### Symptom: `terraform init` is slow or re-downloads providers every time

**Cause:** No shared plugin cache, so each working directory downloads its own copy.
**Solution:** Set a plugin cache directory in the CLI config (`plugin_cache_dir`) so providers are reused across directories.
**Prevention:** Configure the cache once per machine / CI image. See the Admin Guide for details.

### Error: `Backend configuration changed`

**Cause:** The `backend` block changed since the last `init`.
**Solution:** Run `terraform init -migrate-state` to move state to the new backend, or `-reconfigure` to ignore the old configuration.
**Prevention:** Change backends deliberately and communicate to the team before migrating shared state.

### Error: `Required token could not be found` / 401 from HCP Terraform

**Cause:** Missing or expired credentials for HCP Terraform / Terraform Enterprise.
**Solution:** Run `terraform login` (or `terraform login app.terraform.io`) to obtain and store a token.
**Prevention:** In automation, supply a token via `TF_TOKEN_<host>` or a credentials file rather than interactive login.

---

## 16. Frequently Asked Questions

**Q: Do I need to install providers manually?**
No. Declare them in `required_providers` and `terraform init` downloads them automatically from the registry.

**Q: Should I commit `terraform.tfstate` to Git?**
No. State can contain secrets and is easy to corrupt with merge conflicts. Use a remote backend ([§9](#9-state-and-backends)) for any shared work. You *should* commit `.terraform.lock.hcl`.

**Q: What is the difference between `plan` and `apply`?**
`plan` previews changes and makes no modifications. `apply` executes changes. `apply` without a saved plan re-plans and asks for confirmation first.

**Q: What is the difference between a workspace and a directory of separate configs?**
Workspaces give one configuration multiple state files (good for identical environments). Separate directories give full configuration independence (good when environments diverge). Many teams prefer separate directories for production isolation.

**Q: How do I destroy just one resource?**
`terraform destroy -target=aws_instance.web` (or `terraform apply -destroy -target=...`). Targeting is for exceptional cases — prefer changing the configuration.

**Q: How do I replace a single resource?**
`terraform apply -replace=aws_instance.web`. This is the modern replacement for `terraform taint`.

**Q: What does `count` vs `for_each` mean?**
`count` makes N indexed copies (`web[0]`, `web[1]`). `for_each` makes one instance per map/set key (`web["a"]`), which is more stable when the set changes.

**Q: How do I see the value of an output for use in a script?**
`terraform output -raw NAME` prints the bare string with no quotes or color.

**Q: Can I run Terraform in CI without prompts?**
Yes. Use `terraform init -input=false`, `terraform plan -input=false -out=tfplan`, then `terraform apply -input=false tfplan`. Set `TF_IN_AUTOMATION=1` to tidy the output.

**Q: How do I import existing infrastructure?**
Use an `import` block and `terraform plan -generate-config-out=...` (recommended), or the one-off `terraform import ADDR ID` command. See [§11](#11-importing-existing-infrastructure).

**Q: Why does `terraform plan` want to recreate a resource I only edited slightly?**
Some attributes are "force-new" — changing them requires replacement. The plan shows `-/+`. Use `create_before_destroy` to avoid downtime, and check the provider docs for which attributes force replacement.

**Q: How do I upgrade providers?**
`terraform init -upgrade` installs the newest versions allowed by your `version` constraints and updates the lock file.

**Q: What is the dependency lock file `.terraform.lock.hcl`?**
It records the exact provider versions and checksums selected by `init`, so everyone (and CI) installs identical providers. Commit it.

**Q: How do I format check in CI?**
`terraform fmt -check -recursive` exits non-zero if any file is not in canonical format.

**Q: Is Terraform open source?**
Terraform is licensed under the Business Source License 1.1 (BUSL-1.1), a source-available license with restrictions. It is not OSI-approved open source. Review the license for your use case.

**Q: How do I make Terraform less verbose / debug a problem?**
Set `TF_LOG=DEBUG` (or `TRACE` for maximum detail) and optionally `TF_LOG_PATH=terraform.log` to capture logs to a file.

**Q: Can a plan exit with a special code if there are changes?**
Yes: `terraform plan -detailed-exitcode` returns `0` (no changes), `1` (error), or `2` (changes present) — useful for CI gating.

---

## 17. Quick Reference

### 17.1 Everyday commands

```bash
terraform init                 # set up the working directory
terraform fmt -recursive       # format all configuration
terraform validate             # check validity
terraform plan -out=tfplan     # preview and save a plan
terraform apply tfplan         # apply the saved plan
terraform output -json         # read outputs
terraform destroy              # tear everything down
terraform state list           # list managed resources
terraform workspace list       # list workspaces
terraform version              # show version
```

### 17.2 Exit codes

| Command | Code | Meaning |
|---------|------|---------|
| any | `0` | Success |
| any | `1` | Error |
| `plan`/`apply` with `-detailed-exitcode` | `2` | Success, plan has changes |
| `fmt -check` | non-zero | Some files were not formatted |
| `validate -json` | non-zero | Configuration invalid |

### 17.3 Key files and directories

| Path | Purpose |
|------|---------|
| `*.tf` | Configuration files (HCL). |
| `*.tf.json` | Configuration in JSON syntax. |
| `*.tfvars` / `*.auto.tfvars` | Variable values (`*.auto.tfvars` auto-loaded). |
| `*.tftest.hcl` | Test files for `terraform test`. |
| `terraform.tfstate` | Local state (default backend). |
| `.terraform.lock.hcl` | Provider dependency lock file — **commit this**. |
| `.terraform/` | Working data: installed providers, modules, backend config (`TF_DATA_DIR`). |

---

_Generated June 2026 from hashicorp/terraform at b2fc2ca. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
