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
randomprovider 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). Ifterraform versionreports an older release than you expected, you likely have a second copy earlier on yourPATH; runwhich -a terraform(orwhere terraformon 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 initis always safe to run again. It never destroys configuration or state. Commit the generated.terraform.lock.hclto 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 destroyandterraform apply -auto-approvecan delete real, production infrastructure with no undo. Always review the plan, and protect critical resources withlifecycle { 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'ssourceaddress and acceptableversion.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 -checkandterraform validatemake 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
versionor?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 pushoverwrites 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 envis a deprecated alias forterraform workspace(env list,env select,env new,env delete). Useworkspacein 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:
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 testcreates 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
applywithout 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=ADDRESSovertaint— 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 experimentalterraform cloud,terraform test cleanup, andterraform state migrate(available only in alpha builds with experiments enabled).terraform pushis 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_TLSand the*_DEV_OVERRIDEvariables 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). |