Terraform

Terraform Input Variables: variable Block Design for Production and the Associate Exam

2026-04-19
NicheeLab Editorial Team

Terraform input variables are the public interface of a module. Designed carefully, they boost reusability and prevent real-world incidents like type mismatches or swapped values. Designed sloppily — with vague typing or unclear precedence — unintended values get applied or secrets leak into outputs.

This article walks through variable block type design, validation, sensitive handling, value precedence, and how to pass values across module boundaries — following the official documentation. It also highlights the angles that come up most often on the Terraform Associate exam, with implementation patterns you can drop straight into your own code.

variable Block Basics and Assumptions

A variable block defines an external input to a module. You declare the name, type, description, default, validation, sensitive flag, and so on. A variable without a default is treated as a required input and must be supplied at plan and apply time.

Terraform is declarative: variables do not hold state — they are resolved at evaluation time. So a correct understanding of where values come from and which value ultimately wins (precedence) is the starting point of any solid design.

  • Always write description — it serves as the spec for module consumers
  • Type as strictly as possible (see below). Avoid stuffing everything into string
  • No default means required; having a default makes it optional
  • sensitive masks output and logs, but does not encrypt state

Type Design: When to Use primitive, collection, or structural

Types are the core of variable design. Picking among primitives (string/number/bool), collections (list/set/map), and structural (object/tuple) types eliminates ambiguity in inputs. The distinctions between list vs set and map vs object matter on both the exam and the job.

Choose the strictest type that fits the use case. To avoid future breaking changes, inputs likely to grow are safest defined as an object with an explicit schema.

  • list is ordered and allows duplicates; set is unordered and unique
  • map(string) is a free-key dictionary; object is a fixed-schema structure
  • tuple has fixed position and type — rarely the right pick for external inputs
  • any is a last resort — always combine it with validation for safety
Type / PatternExampleUse cases, gotchas, and exam points
Primitivestring / number / boolSimplest. Flags, names, numbers. Do not confuse bool with strings — use true, not "true".
list vs setlist(string) / set(string)Use list to keep order. Use set (unordered) when you want deduplication and easy pairing with for_each.
map vs objectmap(string) / object({k=...})Use map for free-key things like tags. Use object when you want to lock the schema and prevent breaking changes later.
tuple vs listtuple([string, number])Fixed position and type. Tough to maintain as an external input. For the exam, remember fixed-length and position-dependent.
anytype = anyFlexible but unsafe. If you must use it, constrain with validation. On the exam, remember strict typing is recommended.

Designing default, nullable, sensitive, and validation

Input safety and developer experience depend heavily on attribute design. With a default an input becomes optional; without one it is required. Use the validation block to spell out the accepted range and catch errors early. The sensitive flag masks plan output and UI displays, but does not hide values in state (encrypt the backend separately).

Terraform 1.x lets you use nullable to control whether null is accepted. Behavior details can vary across provider and language versions, so it is safest to pin your team's version before adopting it.

  • Make validation's error_message specific
  • Never pass sensitive values straight into output
  • When using default=null, centralize the fallback in locals
  • Check numbers and ranges explicitly (>=, <=, etc.)

variable block example: type constraints, validation, sensitive, and null strategy

variable "environment" {
  type        = string
  description = "デプロイ環境: dev/stg/prod"
  validation {
    condition     = contains(["dev", "stg", "prod"], var.environment)
    error_message = "environment は dev, stg, prod のいずれかにしてください。"
  }
}

variable "instance_count" {
  type        = number
  description = "作成するインスタンス数"
  default     = 2
  validation {
    condition     = var.instance_count >= 1 && var.instance_count <= 20
    error_message = "instance_count は 1〜20 の範囲で指定してください。"
  }
}

variable "tags" {
  type        = map(string)
  description = "共通タグ"
  default     = {}
}

variable "admin_password" {
  type        = string
  description = "管理者パスワード"
  sensitive   = true
}

variable "optional_domain" {
  type        = string
  description = "省略可のカスタムドメイン。未設定なら null"
  default     = null
  nullable    = true
}

variable "network" {
  type = object({
    vpc_id        = string
    subnet_ids    = list(string)
    enable_public = bool
  })
  description = "ネットワーク設定"
}

locals {
  # null や空をフォールバック
  effective_tags = length(var.tags) > 0 ? var.tags : {
    "managed-by" = "terraform"
  }
}

resource "example_instance" "this" {
  count = var.instance_count
  name  = "${var.environment}-app-${count.index}"
  tags  = local.effective_tags
  # 注意: var.admin_password を平文で出力に流さない。
}

Variable Design at Module Boundaries

The key to a reusable module is a stable input interface. Group related values into an object so consumers can pass them in meaningful units. Free-key sets like tags fit map(string); fixed-schema configuration groups fit object.

Manage breaking changes (type changes, switching to required, etc.) through module versioning. Toggling required vs optional has a big consumer impact, so when moving to optional, the safest path is to add a default or accept null and centralize the fallback in locals.

  • Reference module inputs with var. (e.g., var.name)
  • Use object for structures, string + validation for enumerated choices, and bool for boolean branching
  • Pick map/list for variable-length elements; pick set for set operations like deduplication
  • If future extension is likely, leave room in the object for backward-compatible additions

Passing Values and Precedence (tfvars, env vars, CLI)

When a variable has multiple sources, Terraform picks the final value using a fixed precedence. More explicit sources win, and the last one resolved is the one applied. This shows up often on the Associate exam.

Understand the difference between auto-loaded files (terraform.tfvars, *.auto.tfvars) and explicit CLI options (-var, -var-file). The TF_VAR_name environment variable has low precedence and is useful for overriding defaults.

  • When the same key exists in multiple sources, the higher-precedence source wins
  • The .tfvars.json extension is also supported (JSON format)
  • -var is for individual values; -var-file is for grouped values
  • Terraform Cloud / Enterprise workspace variables are a separate system (this article focuses on local execution)

Variable value precedence (left = low → right = high)

default(inside variable)TF_VAR_nameenv varterraform.tfvars(.json)*.auto.tfvars(.json)-var-file=...-var="k=v"default → TF_VAR_name → terraform.tfvars → *.auto.tfvars → -var-file → -var

Anti-patterns and a Review Checklist

Here are the common pitfalls in variable design. Use this as a checklist during code review.

Overusing any, passing booleans as strings, skipping validation, and ignoring sensitive are the classic causes of incidents.

  • Do not pass true/false as a string — use bool
  • Avoid type = any; if you must use it, pair it with strict validation
  • Do not skip validation — cover ranges, allowed values, and formats
  • Do not pipe sensitive values straight to output — also plan separately for state encryption/protection
  • Do not ignore the list vs set distinction (order vs uniqueness)
  • Group related values into an object to keep argument lists from bloating

Check Your Understanding

Associate

問題 1

For the variable name, the variable block defines default="app". You set the environment variable TF_VAR_name=api, place name="web" in terraform.tfvars in the same directory, and then run plan with -var-file=env/dev.tfvars (which contains name="batch"). Which name is ultimately applied?

  1. A. batch
  2. B. web
  3. C. api
  4. D. app

正解: A

Variable precedence is roughly default < env var < terraform.tfvars < *.auto.tfvars < -var-file < -var. So the name="batch" inside -var-file is what ultimately gets applied.

Frequently Asked Questions

Does setting sensitive = true make state safe?

No. The sensitive flag only masks values in plan output and UI displays; it does not encrypt the state file itself. You need to protect state through the backend (for example, Terraform Cloud, encrypted remote backends, or storage-side encryption) and design outputs so they never pass secrets through directly.

Should I use map(string) or object?

Use map(string) for free-key dictionaries like tags. Use object when you want fixed attribute names and types for a configuration group (such as network settings), so future extensions stay schema-managed. For the exam, remember: map means free keys; object means a fixed schema.

What is the best way to pass per-environment values?

Prepare per-environment tfvars files and pass them explicitly, for example -var-file=env/dev.tfvars. This is easy to manage in practice and the precedence is clear. Put shared values in terraform.tfvars and apply individual overrides with -var or the TF_VAR_name environment variable.

Check what you learned with practice questions

Practice with certification-focused question sets

無料で問題を解いてみる
Author

NicheeLab Editorial Team

NicheeLab editorial team focused on data engineering and cloud certification learning. Content is structured around practical study needs and official exam domains.


Related articles
Terraform

HCL Syntax: Terraform's Configuration Language (2026)

HCL2 fundamentals for Terraform — blocks, attributes, expres...

Terraform

Terraform Authoring & Operations Pro: Complete Guide (2026)

Tactics for the Terraform Pro exam — module authoring, works...

Terraform

Terraform Providers: Plugin Management Fundamentals (2026)

Provider mechanics — required_providers, versions, mirrors, ...

Terraform

Terraform Resource Blocks: Declarative Infra Units (2026)

Resource block fundamentals — addresses, references, common ...

Terraform

Terraform Data Sources: Read-Only External Data (2026)

Data source basics — declaration, refresh behavior, dependen...

Browse all Terraform articles (102)
© 2026 NicheeLab All rights reserved.