CLI Essentials
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production; see the section bibliography for the reference material consulted while preparing these pages. |
The terraform binary is the one tool every Terraform workflow goes through: a developer at a laptop, a pipeline
runner and a platform such as HCP Terraform all end up running the same handful of commands. This page is the
reference for them. It follows the grouping used by the official command reference: the five main commands
(init, validate, plan, apply, destroy), then all other commands (alphabetical in the help output, grouped
by purpose here), then the global options, the CLI configuration file and the environment variables. It closes
with the deprecated commands and flags and what replaces each of them.
It is written against Terraform 1.17 and OpenTofu 1.13, the baseline of this section. Flag lists were checked against
the 1.17 command reference; where OpenTofu behaves differently, the difference is called out next to the command and
collected in Terraform and OpenTofu differences. The page sits early in the reading path: it assumes the first
run described in Getting Started with Terraform and is the companion
of the dependency graph and the plan (what plan and apply
do internally), State (what the state commands manipulate) and
Backends and Remote State (what init -migrate-state moves).
The shell examples use the Bookshelf scenario (catalog-api, reviews-api, web, PostgreSQL, Redis, a covers
bucket). Output blocks are abbreviated and marked as illustrative: the exact text of Terraform output changes between
minor versions, which is why automation should use the -json and -detailed-exitcode interfaces described below and
never parse human-readable output.
The shape of every command
Every invocation has the same structure:
terraform [global options] <subcommand> [options] [arguments]
Global options (-chdir, -help, -version) go before the subcommand; options of the subcommand go after it. Run
terraform with no arguments to list the commands your installed version supports, and terraform <command> -help
(or terraform -help <command>) for the short inline help of one of them. The inline help describes the most
important characteristics of each command; the website reference is more detailed.
The commands fall into four families, which is a more useful mental model than the alphabetical help output:
| Family | Commands |
|---|---|
The core loop |
|
Inspect and explore |
|
Change what Terraform believes |
|
Housekeeping and integration |
|
Two properties are worth knowing before the details. First, most commands that touch state take a state lock for the
duration of the operation and accept -lock=false and -lock-timeout=DURATION; the lock is covered in
State. Second, every command that evaluates the configuration accepts -var and
-var-file, because evaluating the configuration needs the root module’s input variables; the formats are described
under Input variables on the command line.
Source: Basic CLI features
Global options
| Option | Meaning |
|---|---|
|
Switch to a different working directory before running the subcommand. Every file Terraform would normally read or
write in the current directory (the configuration, |
|
Print the help output, or the help of the subcommand that follows. |
|
An alias for the |
-chdir replaces an old pattern: Terraform 0.13 and earlier accepted a directory as a positional argument of plan,
apply and some other commands. That was deprecated in 0.14 and removed in 0.15, and -chdir is what to use in
wrapper scripts and pipelines. If you need .terraform/ written somewhere other than the target directory (a
read-only checkout, for instance), combine it with TF_DATA_DIR.
terraform -chdir=environments/production init
terraform -chdir=environments/production plan -out=tfplan
terraform -chdir=environments/production apply tfplan
Shell completion
For bash and zsh, terraform -install-autocomplete adds the completion hook to your shell profile and
terraform -uninstall-autocomplete removes it (restart the shell after installing). It is covered where it belongs in
the installation walkthrough of Getting Started with Terraform.
Version and security checks
CLI commands talk to HashiCorp’s Checkpoint service to learn whether a newer version exists and whether there are
security bulletins for the running one; terraform version shows the result. Only an anonymous identifier is sent. Set
the environment variable CHECKPOINT_DISABLE to any non-empty value to disable it for all HashiCorp products, or use
the CLI configuration settings disable_checkpoint and disable_checkpoint_signature (see
The CLI configuration file). Air-gapped CI runners should disable it to avoid timeouts on blocked networks.
Main commands
init
terraform init prepares a working directory. It is the first command to run after writing a configuration or
cloning one, and it is always safe to run again: it never deletes your configuration or state, although later runs
can report errors when settings changed. It performs four jobs in this order:
-
Optionally copies a source module into an empty directory (
-from-module=MODULE-SOURCE). -
Initializes the backend named in the
terraformblock. -
Installs child modules referenced by
moduleblocks. -
Installs provider plugins and writes the dependency lock file
.terraform.lock.hcl.
| Option | Effect |
|---|---|
|
Ask for input if necessary; when false, error instead of prompting. |
|
Disable state locking during state-related steps, or wait for a lock. The default timeout is |
|
Disable colour codes; enable the machine-readable JSON UI output. |
|
Upgrade modules and providers. For providers it ignores the selections recorded in the lock file and installs the newest version that satisfies the constraints; for modules it re-fetches already-installed modules. |
|
Skip backend configuration. Some later steps need an initialized backend, so use it only on a directory that was
initialized before. The common legitimate use is |
|
Supply partial backend configuration (a |
|
Disregard any existing backend configuration and initialize against the new one without migrating state. |
|
Copy existing state to the newly configured backend; may prompt to confirm the migration of workspace states. |
|
Suppress the migration prompts and answer "yes"; it also enables |
|
Skip child module installation. |
|
Read provider plugins only from this directory, as if it were a |
|
Set the dependency lock file mode. The only documented value is |
|
Copy a module (a version control source or an example) into an empty directory before initializing it. |
Changing the backend configuration requires either -reconfigure or -migrate-state; plain init refuses and tells
you so. The difference is the one that matters in practice: -migrate-state carries your state to the new location,
-reconfigure abandons the link to the old one and starts the new backend as it finds it. Use -backend-config in
pipelines to inject the bucket, prefix or key per environment without editing files:
# Local, first use: install providers and modules, create .terraform.lock.hcl
terraform init
# CI: no prompts, backend settings injected per environment, lock file must not change
terraform init -input=false -backend-config=env/staging.s3.tfbackend -lockfile=readonly
# Move state from the local backend to a newly added remote backend block
terraform init -migrate-state
# Re-point to a different backend without moving state (the old state stays where it was)
terraform init -reconfigure -backend-config="bucket=bookshelf-tfstate-eu-west-1"
# Pick up newer provider and module versions within the constraints, then review the lock file diff
terraform init -upgrade
Source: terraform init command reference
Two more points from the 1.17 reference. Input variables referenced in a module block’s source or version
arguments are not available during init, so such a variable must declare const = true (the variable block gained
the const argument for exactly this purpose); see Modules and
Variables, Outputs and Locals. And the option -get-plugins=false
no longer exists (removed in 0.15), having been superseded by the provider_installation and plugin_cache_dir
settings described below. The lock file and provider selection are the subject of
Providers; commit .terraform.lock.hcl, never .terraform/.
Source: init: child module installation and variable block: const
validate
terraform validate checks whether the configuration is syntactically valid and internally consistent: attribute
names, value types, references. It ignores variable values and existing state, does not contact remote services
(remote state, provider APIs) and is therefore safe as an editor post-save hook or a CI step. It needs an initialized
directory so that providers and modules are available.
| Option | Meaning |
|---|---|
|
Machine-readable result, always without colour. Top-level keys are |
|
Plain output. |
|
Set root module variables (optional; validation does not depend on their values). |
terraform init -backend=false
terraform validate
terraform validate -json | jq '.valid, .error_count'
To validate in the context of a particular workspace and set of variable values use plan, which includes an implied
validation. Custom conditions (precondition, postcondition, variable validation, check) are evaluated by
plan and apply, not by validate; see Lifecycle,
Validation and Checks.
plan
terraform plan creates an execution plan: it refreshes the state from the remote objects (by default), compares the
configuration with the prior state and proposes changes, without carrying any of them out. Its options are grouped by
the reference into planning modes, planning options and other options. The conceptual side (how the graph is walked,
what a plan contains, -replace versus -target, drift) is in
Dependency Graph and Plan; this section lists what each flag
does.
Planning modes are mutually exclusive:
| Mode | Meaning |
|---|---|
normal (default) |
Make the remote objects match the configuration. |
|
Plan to destroy every remote object managed by the configuration. Same as |
|
Plan only to update state and root module outputs to match changes made outside Terraform (for instance during an
incident). It is the replacement for the deprecated |
Planning options (shared with apply when it plans):
| Option | Meaning |
|---|---|
|
Skip synchronising state with the remote objects. Faster, but external changes are ignored and the plan can be wrong. Not allowed in refresh-only mode. |
|
New in 1.17. Refresh only the resource instances that the plan intends to change: Terraform first compares the
configuration with the prior state and skips the refresh of instances with no planned change. Each instance decides
independently. It has the same blind spot as |
|
Plan to replace the instance with this address even if nothing requires it (a degraded object, immutable
infrastructure). Repeat to replace several. Cannot be used with |
|
Focus the plan on the matching instances and everything they depend on. For exceptional situations only (recovering
from mistakes, working around limitations): routine use leads to undetected drift. A resource address selects one
instance ( |
|
Set root module variables; repeatable. |
|
Plan only the invocation of one provider action and exclude all other configuration. See Provisioners, Actions and Post-apply Operations. |
Other options:
| Option | Meaning |
|---|---|
|
Save the plan to an opaque file that |
|
Exit status 0 for success with an empty diff, 1 for an error, 2 for success with a non-empty diff. The basis of drift detection in pipelines. |
|
Experimental. For |
|
Show only warning summaries, unless an error accompanies them. |
|
Do not prompt for unassigned root variables; fail instead. |
|
Machine-readable JSON UI output; implies |
|
State locking controls. Disabling the lock is dangerous if others can run commands against the same workspace. |
|
Plain output. |
|
Limit concurrent graph operations; the default is 10. |
For configurations that use the local backend only, plan also accepts the legacy -state option (see
Deprecated commands and flags).
# Interactive
terraform plan
# Pipeline: saved plan, no prompts, machine-readable exit status
terraform plan -input=false -out=tfplan -detailed-exitcode
echo $? # 0: no changes, 1: error, 2: changes present
# Drift check only: what changed outside Terraform?
terraform plan -refresh-only
# Replace a degraded container instead of using the deprecated taint command
terraform plan -replace='docker_container.catalog_api'
Source: terraform plan command reference
Input variables on the command line
-var 'NAME=VALUE' is parsed twice, by your shell and then by Terraform. Primitive types (string, number, bool)
take the bare value; every other type, including list, map, set and any, takes a Terraform expression. Terraform
errors if there is a space around the equals sign. Prefer -var-file for anything but trivial values, because it
avoids quoting problems entirely. The official recommendation for Windows is the Command Prompt rather than
PowerShell, which cannot pass literal quotes to external programs correctly.
# Unix-style shell: single quotes keep the value literal
terraform plan -var 'region=eu-west-1'
terraform plan -var 'covers_bucket_tags=["web","catalog"]'
# Windows Command Prompt: double quotes, with escaped inner quotes
terraform plan -var "covers_bucket_tags=[\"web\",\"catalog\"]"
# Preferred for anything non-trivial
terraform plan -var-file=staging.tfvars
The other ways of assigning variables (terraform.tfvars, *.auto.tfvars, TF_VAR_ environment variables) and their
precedence are covered in Variables, Outputs and Locals.
apply
terraform apply executes the operations of a plan. It has two modes:
-
Automatic plan mode (no plan file): Terraform creates a new plan as if you had run
plan, shows it and asks for confirmation. All planning modes and planning options are accepted.-auto-approveskips the prompt. -
Saved plan mode (a plan file argument): Terraform performs exactly the saved operations and does not prompt, because passing the file is the approval. No planning modes or options may be added, since the file already holds the result of those decisions, and
-auto-approveis ignored.
| Option | Meaning |
|---|---|
|
Skip the interactive approval. Applies to destructive changes too, so in automation produce and review a plan first. |
|
Same as for |
|
Disable all prompts. Because that includes the approval prompt, an apply without |
|
JSON UI output. Implies |
|
Concurrent operations while walking the graph; the default is 10. |
|
Replace the instance with this address (automatic plan mode). |
|
Invoke a single provider action; instances of a |
all planning modes and options |
Only without a saved plan. |
If an error occurs during an apply, Terraform logs it, saves the changes made so far to state, releases the lock and
exits. There is no automatic rollback: fix the cause and apply again, and the configuration converges from wherever
the run stopped. For the local backend only, apply still accepts the legacy -state, -state-out and -backup
options.
# Interactive: plan, review, confirm
terraform apply
# Two-step, the form to use in automation
terraform plan -input=false -out=tfplan
terraform show tfplan # review the saved plan
terraform apply -input=false tfplan # no prompt: the file is the approval
# One-shot refresh-only reconciliation with a prompt
terraform apply -refresh-only
# Replace one object
terraform apply -replace='docker_container.reviews_api'
destroy
terraform destroy deprovisions every object managed by the configuration. It is documented as a convenience alias
for terraform apply -destroy, so it accepts most apply options but no plan file and forces destroy mode. To preview
without acting, run terraform plan -destroy. -target can limit the destruction to one resource and its
dependents, with the same caveats as for plan.
terraform plan -destroy -out=destroy.tfplan
terraform apply destroy.tfplan
# Tear down the local Bookshelf stack when finished
terraform destroy
# Exceptional: one resource and what depends on it
terraform destroy -target='docker_container.web'
Resources protected with prevent_destroy make the plan fail; see
Lifecycle, Validation and Checks.
Inspecting configuration, state and plans
show
terraform show [options] [file] renders a state file or a saved plan file in human-readable form; with no file it
shows the latest state snapshot. Use it to inspect a plan before applying it. -json produces the machine-readable
representation (the format is documented in the JSON output format reference and consumed by policy tools and
scanners; see Machine-readable Output and
Integrations). -no-color is the only other option. Be careful: show -json prints sensitive values in plain text.
terraform show # current state, human-readable
terraform show tfplan # a saved plan
terraform show -json tfplan | jq '.resource_changes[] | {address, actions: .change.actions}'
When a provider’s schema version changed since the state was written, upgrade the state first so that show -json can
render it; a plan must have been created without -refresh=false to be shown as JSON.
Source: terraform show command reference
output
terraform output [options] [NAME] prints the root module’s outputs from state: all of them, or the one named. Only
root module outputs are available; to see a child module’s output, re-export it from the root.
| Option | Meaning |
|---|---|
|
Stable JSON, one key per output (or only the named one); pipe to |
|
Print a single string, number or boolean value with no quoting or newline, ideal for shell scripts. It fails for
complex values; use |
|
Plain output. |
|
Legacy option for the |
By name or with -json/-raw, sensitive outputs are shown in plain text; the unqualified listing shows
<sensitive>. Ephemeral outputs are never stored in state, so output cannot print them. Use -json or -raw in
automation, because the default human-readable layout may change.
terraform output
terraform output -raw covers_bucket_name
DB_HOST=$(terraform output -raw postgres_endpoint)
terraform output -json service_urls | jq -r '.catalog_api'
console
terraform console opens an interactive prompt to evaluate expressions against the configuration and the current
state of the selected workspace. It is the quickest way to test a for expression, a cidrsubnet call or a regular
expression. It holds the state lock while open and reads remote state first. Leave it with exit, Control-C or
Control-D.
| Option | Meaning |
|---|---|
|
Evaluate in the scope of a child module instead of the root, for example |
|
Generate a plan first and evaluate against the planned state, so values of not-yet-created resources are more
complete. Slower, and planning can run side effects in unusual configurations (an |
|
Legacy option for the |
Non-interactive use works by piping newline-separated expressions in; only the final result is printed.
echo 'cidrsubnet("10.0.0.0/16", 8, 3)' | terraform console
terraform console -scope='module.network'
graph
terraform graph emits the dependency graph in the DOT language of Graphviz. The default output is a simplified
graph of resources and data sources showing only dependency ordering; -type selects a more detailed graph of the
internal operation, at the cost of exposing implementation details.
| Option | Meaning |
|---|---|
|
One of |
|
Graph for applying the given saved plan; implies |
|
Highlight cycles with coloured edges; only with a real graph operation type. Useful for "Cycle" errors. |
|
Root module variables. |
terraform graph | dot -Tsvg > graph.svg
terraform graph -type=plan -draw-cycles | dot -Tpng > plan-graph.png
Graphviz’s dot renders the result. The meaning of the graph is explained in
Dependency Graph and Plan.
providers, modules, version and metadata
terraform providers shows the provider requirements of the configuration in the current directory and where each
requirement came from (which module declared it), as a tree. It accepts -var/-var-file and has three
subcommands:
| Subcommand | Purpose |
|---|---|
|
Add provider selections and checksums to the lock file without installing providers. Options: |
|
Download the required providers into a directory with the layout of a filesystem mirror (and index files usable as a
network mirror). Options: |
|
Print the full schemas of the providers in use, as JSON. |
terraform modules (Terraform 1.10 and later) lists every module declared in the configuration with its key, source
and version, as a tree or, with -json, as a machine-readable list (format_version is 1.0); it is aimed at auditing
and policies on module consumption. terraform get [-update] downloads or updates the modules declared in the root
module into .terraform/ (init does this too; get is the module-only variant).
terraform version [-json] prints the Terraform version, platform, selected providers and, unless Checkpoint is
disabled, upgrade and security information; with -json it omits upgrade information. terraform metadata functions
-json (Terraform 1.4 and later) prints the signatures of all functions available in the running version, which editor
tooling uses; -json is required.
terraform providers
terraform providers lock -platform=linux_amd64 -platform=darwin_arm64 -platform=windows_amd64
terraform providers mirror -platform=linux_amd64 ./provider-mirror
terraform modules -json | jq '.modules[].source'
terraform version -json | jq -r '.terraform_version'
The registry, tiers and lock file are explained in Providers; the use of mirrors in air-gapped environments is shown under Provider installation.
fmt
terraform fmt [options] [target...] rewrites configuration files to the canonical style (a subset of the language
style conventions plus minor readability adjustments). With no target it scans the current directory; the target can
be a directory, a file or - for standard input. It is intentionally opinionated and has no style options. The canonical
format can change slightly between versions, so run fmt after upgrading Terraform.
| Flag | Meaning |
|---|---|
|
Do not list files whose formatting differs. |
|
Show the diffs of the changes. |
|
Do not overwrite files (implied by |
|
Exit with status 0 only if the input is already formatted; otherwise exit non-zero and list the offending files. The CI gate. |
|
Process subdirectories too; by default only the given directory. |
|
Plain output. |
terraform fmt -recursive # fix in place
terraform fmt -check -recursive -diff # CI: fail if anything would change, show what
Source: terraform fmt command reference
Changing state and workspaces
The state family operates on the state of the selected workspace in the configured backend. State surgery is a last
resort; for renames and restructuring, moved, removed and import blocks leave a reviewable trail in the
configuration and are preferred (see Refactoring: moved,
removed and import). The state file, locking and the dangers of push are in State.
state
| Command | Purpose |
|---|---|
|
List resource instances in state, optionally filtered by address; |
|
Show the attributes of one instance, human-readable or with |
|
Print the resource identities recorded in state, for resource types whose provider supports identity; |
|
Move an instance or module to another address, within a state or (with |
|
Stop managing instances without destroying the remote objects. |
|
Rewrite the provider source address recorded in state, for example after a provider moved namespaces.
|
|
Write the current state to standard output, whatever the backend. Redirect it to a file to take a backup. |
|
Overwrite the remote state with a local file. Safety checks guard against differing lineage and a higher remote
serial; |
terraform state list
terraform state show 'docker_container.catalog_api'
terraform state pull > backup-$(date +%Y%m%d).tfstate # always before surgery
terraform state mv 'docker_container.api' 'docker_container.catalog_api'
terraform state rm 'docker_container.legacy_worker'
Quote addresses that contain brackets or double quotes so the shell does not interpret them. Resource address syntax is described in the state documentation.
import
terraform import [options] ADDRESS ID associates an existing remote object with a resource address in state. The
ID format is provider-specific and documented per resource. It does not write configuration: you must have a
matching resource block first. Import each remote object into exactly one address. Options include -config=path
(the directory holding the provider configuration, default the working directory), -input, -lock, -lock-timeout,
-parallelism, -var, -var-file, and the deprecated -provider. For local backends the legacy -state,
-state-out and -backup options apply.
The reference itself recommends the declarative alternative: import blocks, optionally with plan
-generate-config-out, run during an ordinary apply and reviewable in a pull request. Discovery of existing resources
through .tfquery.hcl files is the query command below. Both are covered in
Import and Query.
terraform import 'aws_s3_bucket.covers' bookshelf-covers-example
query
terraform query runs the list blocks of the
.tfquery.hcl files in the current directory against the remote infrastructure and prints the discovered resources,
so that they can be imported.
| Option | Meaning |
|---|---|
|
Generate |
|
Machine-readable output. |
|
Variables defined in the |
|
Documented as requiring Terraform 1.17.0 or later, in HCP Terraform: select a policy-set directory in the uploaded configuration to evaluate
against the discovered resources; repeatable. Applicable workspace policy sets are evaluated even without the flag,
and query-time evaluation does not evaluate Sentinel or OPA policies. Resource identities alone are not enough
policy input, so |
|
Plain output. |
terraform query
terraform query -generate-config-out=to-import/generated.tf
workspace
terraform workspace manages the named states of one configuration (CLI workspaces). They are not the same thing as
HCP Terraform workspaces; the limits of CLI workspaces and when to prefer directories are discussed in
Workspaces, Environments and Project
Structure.
| Command | Purpose |
|---|---|
|
List workspaces; the current one is marked with an asterisk. |
|
Print the current workspace name (the default is |
|
Create and select a workspace. |
|
Select a workspace; |
|
Delete a workspace. Refuses if it still tracks resources unless |
The selected workspace can be overridden with TF_WORKSPACE, which is recommended only for non-interactive use because it is easy to forget it is set.
terraform workspace new staging
terraform workspace select -or-create production
terraform workspace list
force-unlock
terraform force-unlock [options] LOCK_ID removes a lock on the state of the current configuration. It does not
modify infrastructure. The lock ID is printed in the "Error acquiring the state lock" message. The only option is
-force, which skips the confirmation. A local state file cannot be unlocked from another process, and how the lock
is held depends on the backend. Use it only after confirming that no run is still active: unlocking under a running
apply invites state corruption.
terraform force-unlock 6a2c0f12-0000-0000-0000-000000000000 # placeholder lock ID
Authentication, testing and Stacks
login and logout
terraform login [hostname] obtains an API token for HCP Terraform, Terraform Enterprise or another host implementing
the login protocol, by opening a web browser on the same machine; it is interactive only. Without a hostname it uses
app.terraform.io, and organisations in HCP Europe use the hostname app.terraform.io/eu. By default the token is saved in
plain text in credentials.tfrc.json in the Terraform data directory of the user (the command tells you the exact
location before saving and lets you cancel); a credentials helper can store it elsewhere. terraform logout [hostname]
removes the stored credentials locally (or calls the helper’s forget) but does not revoke the token on the server.
For unattended runs, do not use login: configure credentials blocks, or better the TF_TOKEN_ environment variables
described in Credentials.
Source: terraform login and terraform logout
test
terraform test runs the .tftest.hcl files of a module: it searches the current directory and the tests
directory, runs the run blocks as plan or apply operations against in-memory state (separate from the real
state) and then destroys what it created. Because it creates real infrastructure unless mocks or command = plan are
used, run it in dedicated accounts and watch the cleanup output.
| Option | Meaning |
|---|---|
|
Limit the run to specific test files; repeatable. |
|
Directory to search for test files instead of |
|
Print the plan or state of every |
|
Machine-readable results. |
|
Write a JUnit XML report; incompatible with |
|
Concurrent plan/apply operations within one run; default 10. |
|
Concurrent |
|
Run the tests remotely in HCP Terraform against a module from the private registry; run |
terraform test
terraform test -filter=tests/validations.tftest.hcl -verbose
terraform test -junit-xml=test-report.xml
Writing tests, mocks and the CI integration are in Testing.
Source: terraform test command reference
stacks
terraform stacks manages Terraform Stacks (the multi-deployment orchestration layer of HCP Terraform). It is a
separate plugin-backed command with its own global flags and is described conceptually in
Terraform Stacks. The reference lists these commands and subcommand
groups:
| Command | Purpose |
|---|---|
|
Download remote modules, sub-Stacks and providers and prepare the component configuration (creates |
|
Validate the component and deployment configuration locally; run |
|
Rewrite |
|
Create or update |
|
Create a Stack in HCP Terraform: |
|
List Stacks in an organization, optionally filtered by |
|
Retrieve warnings and errors for a configuration (ID prefix |
|
Print the Stacks plugin version. |
|
Subcommand groups to manage configuration versions, deployment runs and groups, and to migrate traditional workflows to Stacks. |
Global flags for all stacks commands are -chdir, -plugin-cache-dir, -no-color, -usage and -stacks-version.
On Terraform Enterprise, set TF_STACKS_HOSTNAME to the hostname of the instance.
Source: terraform stacks reference
The CLI configuration file
The CLI configuration file holds per-user settings that apply to every working directory, as opposed to the infrastructure configuration. It uses HCL syntax with its own attributes and blocks. Its location depends on the operating system:
-
on Windows,
terraform.rcin the user’s%APPDATA%directory; -
everywhere else,
.terraformrcdirectly in the home directory; -
anywhere, a file named by the
TF_CLI_CONFIG_FILEenvironment variable, which should follow the naming pattern*.tfrc.
Windows Explorer can hide extensions: terraform.rc.txt is not recognised even if it is displayed as terraform.rc.
Because the file is processed before -chdir, it is not affected by that option.
# ~/.terraformrc (illustrative; no real tokens)
plugin_cache_dir = "$HOME/.terraform.d/plugin-cache"
disable_checkpoint = true
credentials "app.terraform.io" {
# Prefer the TF_TOKEN_app_terraform_io environment variable in CI instead of writing a token here.
token = "REPLACE_WITH_USER_OR_TEAM_TOKEN"
}
The available top-level settings are:
| Setting | Meaning |
|---|---|
|
A block with a |
|
At most one block selecting an external helper program (optional |
|
Disable the version and bulletin checks, or only the anonymous identifier sent with them. |
|
Enable the shared provider plugin cache and name its directory. |
|
Transitional setting (also available as an environment variable) that lets the cache satisfy a provider even if the lock file lacks an entry. |
|
Customise where |
Credentials
Terraform expects API tokens for services such as the private registry and remote operations in credentials
blocks, which terraform login writes for you. The token must be a user token or a team token; organisation tokens
cannot be used for CLI actions. The credentials hostname must match the hostname used in module sources and backend
configuration.
Since Terraform 1.2 a token can instead be supplied with an environment variable named TF_TOKEN_ plus the hostname
with periods replaced by underscores, for example TF_TOKEN_app_terraform_io for app.terraform.io. Hostnames with
non-ASCII characters must be converted to punycode, and hyphens (usually invalid in variable names) may be written as
double underscores. The order of preference is the environment variable first, then the credentials block (including
what terraform login stored), then a configured credentials helper. This is the right pattern for pipelines: inject
the secret from the CI secret store and nothing is written to disk.
# CI job: token comes from the secret store, never committed (placeholder shown)
export TF_TOKEN_app_terraform_io="${HCP_TERRAFORM_TOKEN}"
terraform init -input=false
Source: CLI configuration: credentials
Provider installation
By default init downloads providers from their origin registries. The provider_installation block overrides that
with a list of installation methods, each optionally restricted by include and exclude patterns (exclusions win;
the registry.terraform.io/ prefix may be omitted, and */* means registry.terraform.io/*/*):
| Method | Meaning |
|---|---|
|
Ask the origin registry and download from the location it names. No arguments. |
|
Look in a local directory (required |
|
Ask an HTTPS server implementing the provider network mirror protocol (required |
|
A block mapping provider addresses to local directories, bypassing version and checksum checks, for provider
development. It must come first, and |
# ~/.terraformrc: serve the internal registry from a local mirror, everything else directly
provider_installation {
filesystem_mirror {
path = "/usr/share/terraform/providers"
include = ["registry.example.internal/*/*"]
}
direct {
exclude = ["registry.example.internal/*/*"]
}
}
If the file has no provider_installation block, Terraform builds an implied configuration: filesystem mirrors for the
existing per-OS plugin directories (for example $HOME/.terraform.d/plugins on Linux and macOS, terraform.d/plugins
in the working directory if present) plus an implied direct method that excludes the providers found in those
mirrors. To use a mirror exclusively, remove direct or exclude the providers from it.
The plugin cache avoids downloading the same provider for each working directory: set plugin_cache_dir (the
directory must already exist; on Windows use forward slashes) or the TF_PLUGIN_CACHE_DIR environment variable.
The cache directory must not also be a filesystem mirror, Terraform never deletes entries from it, and it is not
guaranteed to be safe for concurrent init runs. By default a cached package is used only if it matches a checksum
in the lock file.
Environment variables
Terraform reads a number of environment variables. Most are for CI and debugging; none is required for normal use. The table lists the ones documented in the 1.17 environment variable reference.
| Variable | Effect |
|---|---|
|
Enable detailed logs on stderr. Levels, in decreasing verbosity: |
|
Enable logging separately for Terraform core and for provider plugins, with the same levels. |
|
Append the log to this file. Logging still requires |
|
When |
|
Set the root module input variable |
|
Extra arguments inserted after the subcommand and before arguments typed on the command line (so the command line wins). Parsed like shell input, so quotes group arguments. |
|
Same, for the subcommand |
|
Where per-working-directory data such as the backend configuration is stored; the default is |
|
Select a workspace without |
|
Any non-empty value stops Terraform suggesting follow-up commands. Cosmetic; do not parse output. |
|
Maximum retries of the registry client for connection errors and retry-safe 5xx responses. |
|
Timeout in seconds for registry requests; the default is 10. |
|
How often (seconds) state is persisted to a remote backend during |
|
Path to the CLI configuration file. |
|
Alternative to the |
|
Any value other than empty or |
|
Bearer token for the Terraform-services host |
|
Any non-empty value disables the version and bulletin checks. |
|
Defaults for the |
|
Documented on the S3 backend page: text appended to the User-Agent header of the AWS requests made by that backend. |
For the TF_LOG family, pipelines usually combine TF_LOG=DEBUG with TF_LOG_PATH and upload the file as a build
artifact only on failure; the log can contain sensitive data, so treat it accordingly. Provider authentication
variables (AWS_*, ARM_*, GOOGLE_*) belong to the providers and are covered with each cloud.
# A typical pipeline preamble
export TF_IN_AUTOMATION=true
export TF_INPUT=0
export TF_CLI_ARGS_plan="-lock-timeout=60s"
export TF_PLUGIN_CACHE_DIR="$HOME/.terraform.d/plugin-cache"
export TF_VAR_db_password="${DB_PASSWORD}" # injected from the secret store, never committed
terraform init
terraform plan -out=tfplan
Source: Environment variables, Enable Terraform logs and S3 backend
Deprecated commands and flags
| Deprecated | Use instead |
|---|---|
|
|
|
Still the way to clear a taint that Terraform set itself after a failed multi-step create; if you later find the
object degraded after all, replace it with |
|
|
|
Marked deprecated in the import reference; by default Terraform uses the provider configured for the target resource, which is almost always right. |
|
Legacy options accepted only by the |
a directory argument to |
|
|
|
|
|
|
|
What changed recently
-
1.17 (as documented in the 1.17 reference):
planandapplygained-minimal-refresh;terraform querygained-policiesfor query-time policy evaluation in HCP Terraform; the 1.17 reference documents that input variables used in modulesourceandversionmust declareconst = true. -
1.14 and later: the
terraform querycommand andlistblocks in.tfquery.hclfiles (the release that introduced them is documented in Import and Query). -
1.10: the
terraform modulescommand. -
1.4:
terraform metadata functions -json. -
1.2:
TF_TOKEN_environment variable credentials. -
0.15 and earlier:
-chdirreplaced directory arguments, and-refresh-onlyandapply -destroymaderefreshand the separate destroy path unnecessary.
The section What Changed and Migration places these in the wider release history.
Terraform and OpenTofu differences
The tofu binary has the same command structure, and the core loop, state, workspace, import, console,
graph, fmt, validate, show, output, providers and force-unlock behave the same. Differences found in the
OpenTofu command reference (which tracks the development branch, so confirm any flag against the documentation for
your exact version):
-
the binary and the environment prefix:
tofu, with the sameTF_variables (TF_LOG,TF_VAR_,TF_CLI_ARGS,TF_DATA_DIR,TF_WORKSPACEand so on) and additionallyTF_ENCRYPTIONfor state and plan encryption,TF_IGNOREandTF_PROVIDER_DOWNLOAD_RETRY; -
the CLI configuration file keeps its names (
.terraformrc,terraform.rc) andTF_CLI_CONFIG_FILE; -
tofureads configuration from both.tfand.tofufiles (see OpenTofu); -
additional
planandapplyoptions:-exclude,-exclude-file,-target-file,-concise,-json-into,-consolidate-warnings,-consolidate-errors,-show-sensitive,-deprecationand-lint(and-json-intooninit); -
no
terraform query,terraform modulesorterraform stacksequivalents are listed in the OpenTofu command list, and-policies, HCP Terraform integration andterraform loginflows are HashiCorp specific; -
tofu envstill exists as a deprecated form oftofu workspace; -
tofu metadata functionsexists, documented on its own page.
See OpenTofu for the wider comparison, including state encryption and early evaluation.
Where the books are outdated
Older material (including the books this section draws on for concepts) shows habits that the current reference discourages:
-
terraform taintto force a replacement: use-replaceso the plan shows it. -
terraform refreshto sync state: use-refresh-only, which prompts you to review the detected changes. -
-state,-state-outand-backupflags for juggling state files, orstate pull/pushto migrate to a remote backend: change thebackendblock and runterraform init -migrate-state. -
passing a directory to
terraform planorapply: use-chdir. -
parsing the human-readable output of
output,planorshow: use-json,-rawand-detailed-exitcode. -
the term "environment" for
terraform env: useterraform workspace.
Pitfalls
-
Running
apply -auto-approvewithout a reviewed, saved plan: the plan applied is the one computed at that moment, which may differ from the one a reviewer saw. -
Forgetting that saved plan files contain sensitive values in cleartext.
-
Using
-targetas a routine tool, which leaves drift undetected. -
Combining
-varwith unquoted shell special characters; prefer-var-file. -
Leaving
TF_WORKSPACEexported in a shell and applying to the wrong environment. -
Running
init -migrate-statewithout a backup of the existing state (take one withterraform state pull). -
Committing
.terraform/, plan files orcredentials.tfrc.json; commit.terraform.lock.hcl. -
Setting
TF_LOG=TRACEin CI and publishing the log: it can contain secrets.