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 for expressions, conditionals, complex locals, and functions like cidr*, jsonencode, jsondecode, file, etc., without running full plans.
  • 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_each over nested maps/lists behaves oddly, you can print and transform the structures interactively to understand shape and keys before editing code.

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:

  1. Confirm outputs match console:

    terraform output subnet_ips

    You should see the same list printed that you saw for local.subnet_ips in console. Both are derived from the same expression and state.

  2. Confirm password consistency:

    • terraform state show random_password.db will show the result field.
    • Compare that value with random_password.db.result printed 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 state and terraform output.
  • Your locals and for expressions 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 locals or resource arguments.
  • 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.