Environment Variables
Terraform reads a handful of environment variables that let you set input values and tune the CLI without touching any .tf or .tfvars files. This is the cleanest way to inject secrets and per-environment settings in CI pipelines, where committing values to disk is undesirable. Environment variables also override interactive prompts, so automated runs never block waiting for input. Everything here works identically in OpenTofu, which honors the same TF_* names.
Setting input variables with TF_VAR_
Any variable declared with a variable block can be supplied through an environment variable named TF_VAR_<name>. The suffix matches the variable name exactly, including underscores, and is case sensitive.
variable "region" {
type = string
default = "us-east-1"
}
variable "instance_count" {
type = number
}
resource "aws_instance" "web" {
count = var.instance_count
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
}
export TF_VAR_region="eu-west-1"
export TF_VAR_instance_count=3
terraform apply
Because the shell passes everything as a string, Terraform parses the value into the variable’s declared type. The example above coerces "3" into a number automatically. For complex types you provide HCL/JSON-style literals as the value.
export TF_VAR_tags='{ team = "platform", env = "prod" }'
export TF_VAR_subnets='["subnet-a1b2", "subnet-c3d4"]'
terraform plan
Variable values in
.tfvarsand-varflags take precedence overTF_VAR_*. The full order, lowest to highest, is: environment variables,terraform.tfvars,*.auto.tfvars(alphabetical), then-var-file/-varon the command line.
Configuring Terraform behavior
Beyond input values, several environment variables change how the CLI itself runs. These are read at startup and apply to every command in the session.
| Variable | Purpose | Example value |
|---|---|---|
TF_LOG | Enable internal logging at a given level | TRACE, DEBUG, INFO, WARN, ERROR |
TF_LOG_PATH | Write logs to a file instead of stderr | ./terraform.log |
TF_WORKSPACE | Select the workspace without terraform workspace select | staging |
TF_CLI_ARGS | Append arguments to every command | -no-color |
TF_CLI_ARGS_<command> | Append arguments to one command | TF_CLI_ARGS_plan |
TF_INPUT | Disable interactive prompts when set to false | false |
TF_DATA_DIR | Override the .terraform working directory | /tmp/tf |
TF_IN_AUTOMATION | Soften CLI output hints when non-empty | true |
Debugging with TF_LOG
When a plan misbehaves, turn on logging to see provider requests and internal decisions. TRACE is the most verbose; DEBUG is usually enough.
export TF_LOG=DEBUG
export TF_LOG_PATH=./terraform-debug.log
terraform apply
Output:
2026-06-14T09:12:04.118Z [DEBUG] provider.terraform-provider-aws: HTTP Request Sent:
POST https://ec2.eu-west-1.amazonaws.com
2026-06-14T09:12:04.402Z [INFO] Starting apply for aws_instance.web
2026-06-14T09:12:09.871Z [DEBUG] aws_instance.web: apply complete
Unset TF_LOG afterward, since trace logs are noisy and can contain sensitive request data.
Selecting workspaces and injecting flags
TF_WORKSPACE pins the active workspace for the process, which is handy when a single pipeline runs against several environments. TF_CLI_ARGS_<command> lets you standardize flags without editing the command invocation.
export TF_WORKSPACE=staging
export TF_CLI_ARGS_plan="-compact-warnings -parallelism=20"
terraform plan
When environment variables beat files
Files such as terraform.tfvars are ideal for stable, non-secret configuration that belongs in version control. Environment variables shine in the opposite situations: secrets and ephemeral CI context.
| Use case | Prefer env vars | Prefer .tfvars files |
|---|---|---|
| Secrets (passwords, tokens) | Yes — never committed | No — risks leaking into git |
| Per-build CI values | Yes — injected by the runner | No — would need generating |
| Shared, reviewable defaults | No | Yes — visible in diffs |
| Large structured config | No — awkward to quote | Yes — readable HCL |
A typical CI job pulls secrets from a vault and exports them just before the run:
export TF_VAR_db_password="$(vault kv get -field=password secret/prod/db)"
export TF_VAR_api_key="${API_KEY}" # provided by the CI secret store
export TF_INPUT=false
export TF_IN_AUTOMATION=true
terraform apply -auto-approve
Output:
aws_db_instance.main: Creating...
aws_db_instance.main: Creation complete after 4m12s [id=prod-db]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Mark secret variables with
sensitive = trueso their values are redacted in plan output. The environment variable still feeds the value in, but Terraform won’t echo it to logs.
Best Practices
- Prefix only the variable name with
TF_VAR_; the rest must match yourvariableblock character for character. - Pass secrets through
TF_VAR_*from a secrets manager rather than storing them in committed.tfvarsfiles. - Set
TF_INPUT=falseandTF_IN_AUTOMATION=truein CI so runs never hang on a prompt and emit cleaner output. - Keep
TF_LOGoff by default; enable it temporarily for debugging and clearTF_LOG_PATHfiles afterward, as they may contain sensitive data. - Remember that
-varand.tfvarsoverrideTF_VAR_*, so avoid setting the same variable in two places to prevent surprises. - Use
TF_CLI_ARGS_<command>to enforce team-wide flags (like-parallelism) instead of relying on everyone typing them.