Jesse A. Green / notebook

Terraform User Guide

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.

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), 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:

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:

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):

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)

brew tap hashicorp/tap
brew install hashicorp/tap/terraform

Windows (winget)

winget install HashiCorp.Terraform

Linux (Ubuntu/Debian)

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, unzip it, and move the terraform binary into a directory on your PATH (for example /usr/local/bin).

Verify the installation

terraform version
Terraform v1.16.0
on linux_amd64

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

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:

terraform -install-autocomplete

To remove it later:

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.

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.

   ┌──────────┐     ┌────────┐     ┌────────┐     ┌─────────┐
   │  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.

terraform init
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

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:

terraform plan -out=tfplan

4.3 terraform apply — make it real

terraform apply

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

terraform apply tfplan

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

terraform apply -auto-approve

4.4 terraform destroy — tear it down

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).

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.
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:

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

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).
  • cloud — connect to HCP Terraform / Terraform Enterprise instead of a backend.

5.2 Providers

A provider block configures a provider declared in required_providers:

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:

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:

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:

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

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

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).

5.7 The lifecycle meta-argument

Every resource and data block accepts a lifecycle nested block:

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) 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.

# 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.

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
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:

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.


6. Formatting and Validating Configuration

6.1 terraform fmt — canonical formatting

terraform fmt

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

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

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:

terraform init -backend=false
terraform validate

Machine-readable output for editors and CI:

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

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

terraform console
> 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

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

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

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:

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:

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). 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:

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.

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):

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

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:

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.

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:

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.

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

import {
  to = aws_instance.web
  id = "i-0abc123def456"
}
terraform plan -generate-config-out=generated.tf   # optionally generate matching HCL
terraform apply

11.2 The import command (one-off)

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:

terraform test
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. 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.

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

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

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

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).

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:

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) 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) 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.

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

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).