Terraform console is an interactive interpreter where you can evaluate Terraform expressions, inspect state, and prototype logic before committing anything to code or infrastructure.
1. What terraform console actually is
At its core, terraform console is a REPL for Terraform’s expression language (HCL2).
- It reads your configuration and current state from the configured backend, so you can query real values:
var.*,local.*,resource.*,data.*. - It is read‑only with respect to infrastructure: it does not change resources or configuration, it only evaluates expressions against configuration/state or, if you have no state yet, against pure expressions and built‑ins.
- It holds a lock on state while open, so other commands that need the state (plan/apply) will wait or fail until you exit.
Pedagogically, think of it as Terraform’s “maths lab”: you experiment with expressions and data structures in isolation before wiring them into modules.
2. Why you should care as a practitioner
You will use terraform console for three broad reasons:
- Rapid feedback on expressions
- Test
forexpressions, conditionals, complexlocals, and functions likecidr*,jsonencode,jsondecode,file, etc., without running full plans.
- Test
- Insight into “what Terraform thinks”
- Inspect live values for resources, data sources, variables, and outputs as Terraform sees them in state, which is often where misunderstandings hide.
- Debugging complex data structures
- When
for_eachover nested maps/lists behaves oddly, you can print and transform the structures interactively to understand shape and keys before editing code.
- When
This shortens the debug loop significantly on large stacks and reduces the risk of generating enormous, accidental plans.
3. Running the console and basic usage
In any initialized working directory:
terraform init # if not already done
terraform console
You then get a prompt like:
> 1 + 2
3
> upper("auckland")
"AUCKLAND"
You can reference configuration components directly:
> var.cidr
"10.0.0.0/24"
> cidrnetmask(var.cidr)
"255.255.255.0"
> cidrhost(var.cidr, 10)
"10.0.0.10"
Inspecting resources and data sources:
> aws_s3_bucket.data
# prints the entire state object of that bucket (attributes, tags, region, etc.)
Terraform’s own tutorial demonstrates this pattern with an S3 bucket, using terraform console to print attributes like bucket, arn, region, ACLs and so on from state.
To exit:
> exit
Or press Ctrl+D / Ctrl+C.
4. Evaluating expressions: from simple to advanced
The console supports essentially any expression you can write in HCL: literals, operators, functions, for expressions, conditionals, etc.
Examples:
-
Lists and maps:
> [for env in ["dev", "test", "prod"] : "env-${env}"] [ "env-dev", "env-test", "env-prod", ] > { for k, v in { a = 1, b = 2, c = 3 } : k => v if v % 2 == 1 } { "a" = 1 "c" = 3 } -
Filtering complex maps (example adapted from the docs):
variable "apps" { type = map(any) default = { foo = { region = "us-east-1" } bar = { region = "eu-west-1" } baz = { region = "ap-south-1" } } }In the console:
> var.apps.foo { "region" = "us-east-1" } > { for key, value in var.apps : key => value if value.region == "us-east-1" } { "foo" = { "region" = "us-east-1" } } -
Testing network helpers:
> cidrnetmask("172.16.0.0/12") "255.240.0.0" ```[1]
This is exactly how you should design locals and for_each expressions: prototype an expression in console, inspect the result, then paste into your module.
5. Inspecting state and outputs
Console is wired to your current backend and workspace.
-
Inspect an entire resource instance:
> aws_s3_bucket.data # large object showing bucket name, ARN, tags, region, ACL, encryption, etc.The S3 tutorial shows this in detail, where the console prints attributes like
bucket,bucket_domain_name,force_destroy, encryption configuration, tags, and more. -
Build structured objects and validate them:
> jsonencode({ arn = aws_s3_bucket.data.arn id = aws_s3_bucket.data.id region = aws_s3_bucket.data.region }) "\"{\\\"arn\\\":\\\"arn:aws:s3:::...\\\",\\\"id\\\":\\\"...\\\",\\\"region\\\":\\\"us-west-2\\\"}\""
The tutorial uses this pattern to design an output bucket_details, then later validates that terraform output -json bucket_details produces the exact desired JSON structure.
This is a powerful workflow: design your JSON structures interactively in console, then turn them into outputs or policy documents.
6. Using the console with plans (-plan)
By default, console evaluates expressions against the current state, which means values “known after apply” are not concrete yet.
You can ask console to evaluate against a fresh plan:
terraform console -plan
Now you can inspect “planned” values that do not exist in state yet, e.g. resources that are about to be created.
Rationale: this helps reason about the result of for_each, count, and complex expressions before touching real infrastructure. The docs do note that configurations which perform side effects during planning (for example via external data sources) will also do so in console -plan, so such patterns are discouraged.
7. Non‑interactive/scripting usage
You can pipe expressions into console from a script; only the last expression’s result is printed unless an error occurs.
Example from the reference:
echo 'split(",", "foo,bar,baz")' | terraform console
Output:
tolist([
"foo",
"bar",
"baz",
])
This is extremely handy for:
- CI checks that assert a particular expression evaluates to an expected structure.
- One‑off debugging scripts that compute derived values from state (e.g. join tags, summarise regions) without adding permanent outputs.
8. A simple example
Let’s assemble a minimal, end‑to‑end example that you can run locally.
8.1. Configuration
Files:
variables.tf:
variable "cidr" {
type = string
default = "10.0.0.0/24"
}
main.tf:
terraform {
required_version = ">= 1.1.0"
required_providers {
random = {
source = "hashicorp/random"
version = "~> 3.0"
}
}
}
provider "random" {}
resource "random_password" "db" {
length = 16
special = true
}
locals {
subnet_ips = [
for host in range(1, 5) :
cidrhost(var.cidr, host)
]
}
output "db_password" {
value = random_password.db.result
sensitive = true
}
output "subnet_ips" {
value = local.subnet_ips
}
This uses standard providers and functions: random_password, cidrhost, range, and a for expression, all supported in Terraform 1.1+.
8.2. Apply once
terraform init
terraform apply -auto-approve
You now have state with random_password.db and all locals resolved.
8.3. Explore and validate with terraform console
Run:
terraform console
Try these expressions:
> var.cidr
"10.0.0.0/24"
> local.subnet_ips
[
"10.0.0.1",
"10.0.0.2",
"10.0.0.3",
"10.0.0.4",
]
> random_password.db.result
"R@nd0mP@ss..." # your value will be different
Validation steps:
-
Confirm outputs match console:
terraform output subnet_ipsYou should see the same list printed that you saw for
local.subnet_ipsin console. Both are derived from the same expression and state. -
Confirm password consistency:
terraform state show random_password.dbwill show theresultfield.- Compare that value with
random_password.db.resultprinted in console; they must be identical for the same state snapshot.
If both checks pass, you have empirically validated that:
- The console is looking at the same state as
terraform stateandterraform output. - Your
localsandforexpressions behave exactly as expected before you embed similar patterns into more complex modules.
9. Rationale and best‑practice use
From an engineering‑practice perspective, use terraform console as a standard part of your workflow:
- Before adding non‑trivial expressions
- Prototype them in console with realistic variable values; only once you’re happy paste them into
localsor resource arguments.
- Prototype them in console with realistic variable values; only once you’re happy paste them into
- When debugging bugs in production stacks
- Inspect what Terraform actually has in state for a resource or data source, rather than inferring from code.
Used this way, console is not a “nice extra” but a core tool: it turns Terraform’s somewhat opaque expression runtime into something you can interrogate directly and safely.
Leave a Reply