ハブ記事: Terraform Modules: Complete Guide →
Hub article giving you a single-view picture of Terraform modules: design, distribution, and operations
Module input design drives usability, safety, and future extensibility. Vague typing and sloppy defaults are exactly where unplanned drift and production incidents tend to come from.
Following the behavior described in the official Terraform documentation, this article distills the stable concepts the Associate exam likes to test — type constraints, validation, sensitive, tfvars precedence, and so on — into a form you can apply directly in production.
Input variables are the contract surface between the outside and inside of a module. By defining name, type, description, and default carefully, you keep unintended values from flowing in and give consumers an API they do not have to guess at.
At the Associate level you are expected to know the basic variable block properties (type, default, description, sensitive, nullable), var. references, how values are supplied from tfvars / CLI / environment variables, and how type checking works at plan and apply time.
A minimal, readable variables.tf example
variable "name" {
type = string
description = "Logical name (prefix) for the resources created by this module"
}
variable "environment" {
type = string
description = "Environment identifier (e.g., dev, stg, prd)"
}
variable "tags" {
type = map(string)
description = "Common tags. Override with an empty map"
default = {}
}
# Caller-side example
module "app" {
source = "./modules/app"
name = "web"
environment = var.environment
tags = merge(var.tags, { component = "frontend" })
}Tighten types and spell out business constraints with validation. Use nullable to make it explicit whether null is allowed, so you do not end up with bad values slipping in despite having a default.
sensitive is a display suppression, not encryption. The value is still stored in state, so design assuming you also have backend encryption and access control in place.
Practical examples of validation, nullable, and sensitive
variable "environment" {
type = string
description = "One of dev / stg / prd"
validation {
condition = contains(["dev", "stg", "prd"], var.environment)
error_message = "environment must be one of dev, stg, or prd."
}
}
variable "instance_count" {
type = number
description = "Number of instances to launch (1-10)"
default = 2
nullable = false
validation {
condition = var.instance_count >= 1 && var.instance_count <= 10
error_message = "instance_count must be in the range 1-10."
}
}
variable "cidr_block" {
type = string
description = "CIDR notation (e.g., 10.0.0.0/16)"
validation {
condition = can(cidrhost(var.cidr_block, 0))
error_message = "cidr_block must be valid CIDR notation."
}
}
variable "db_password" {
type = string
description = "Database password"
sensitive = true
nullable = false
}For closely correlated values (network settings, scaling settings, etc.), bundling them into a single object type beats letting individual variables proliferate, and it prevents mismatched combinations.
Terraform 1.3+ lets you express optional attributes inside an object directly in the type. That keeps the caller's required fields to a minimum while documenting the default strategy at the type level.
Object with optional attributes (Terraform 1.3+)
variable "network" {
description = "VPC and subnet configuration"
type = object({
cidr_block = string
private_subnets = list(string)
public_subnets = optional(list(string), [])
dns_hostnames = optional(bool, true)
})
}
# Consumer side (inside the module)
locals {
all_subnets = concat(var.network.private_subnets, var.network.public_subnets)
}
# Example of guarding subnet count consistency with validation (where useful)
variable "az_count" {
type = number
description = "Number of AZs to use"
default = 2
validation {
condition = length(var.network.private_subnets) >= var.az_count
error_message = "private_subnets must be at least as long as az_count."
}
}When the same variable has multiple sources, Terraform's precedence decides the final value. The exam loves to test this order. In practice, the steady-state approach is to standardize on per-environment tfvars and only override temporarily on the CLI when you have to.
Auto-loaded files (terraform.tfvars, *.auto.tfvars) are a convenient entry point for team standards. Put long-lived values in tfvars; use the CLI or environment variables for short-lived, one-off values.
| Source | Priority (1 = highest) | Scope | Practical use |
|---|---|---|---|
| -var | 1 | Per command | Temporary overrides and emergency workarounds. Avoid for steady-state operations |
| -var-file=PATH | 1 | Per command | Explicitly pick an environment-specific file. Great for swapping in CI |
| terraform.tfvars / *.auto.tfvars | 2 | Current directory | Standard per-environment configuration; easy to share across the team |
| Environment variable TF_VAR_name | 3 | Shell or CI process environment | Inject secrets or CI-provided values; suppresses log traces |
| variable block default | 4 | Module definition | Use only for safe defaults — never put vague defaults here |
How inputs flow through resolution (high priority → low priority)
CLI flags (-var / -var-file)
|
v (highest priority — overrides everything)
Auto-loaded tfvars (terraform.tfvars / *.auto.tfvars)
|
v
Environment variables TF_VAR_*
|
v
variable block default
|
v
Bundled into the root module's var
|
v
Passed as arguments to module calls
|
v
Applied to resource attributes during plan/applyConcrete examples from production
# Auto-loaded file (team standard)
# terraform.tfvars
environment = "stg"
name = "web"
# Explicitly specified for production only (CI)
terraform apply -var-file=envs/prd.tfvars
# Inject secrets from environment variables (CI or local)
TF_VAR_db_password="s3cr3t" terraform plan
# Temporary override (for debugging)
terraform apply -var instance_count=3Boolean flags like create or enable are convenient, but pile on too many and you get a combinatorial explosion of behaviors. For future extensibility and testability, designs that pass explicit collections (map/list/object) are easier to maintain.
If you really do need a flag, be disciplined about turning things off: use count or for_each to fully skip resource creation, and make sure dependencies do not form cycles.
Flag-based vs collection-based approaches side by side
# Flag-based approach (keep to a minimum)
variable "create_iam_role" {
type = bool
default = true
}
resource "aws_iam_role" "this" {
count = var.create_iam_role ? 1 : 0
name = "example"
assume_role_policy = data.aws_iam_policy_document.assume.json
}
# Collection-based approach (recommended)
variable "iam_roles" {
description = "Map of IAM role definitions to create"
type = map(object({
name = string
assume_role_policy = string
tags = optional(map(string), {})
}))
default = {}
}
resource "aws_iam_role" "this_map" {
for_each = var.iam_roles
name = each.value.name
assume_role_policy = each.value.assume_role_policy
tags = coalesce(each.value.tags, {})
}sensitive is display masking, not state-file encryption. Assume you have remote backend encryption and permission control in place, and propagate sensitive into outputs too.
Variable schema changes are easy to make breaking. Keep existing inputs intact while adding new fields as optional, then mark the old inputs deprecated before removing them in a later step. Document every change in the CHANGELOG and the variable's description.
Examples of propagating secrets and marking deprecations
# Sensitive input and output
variable "db_password" {
type = string
description = "Database password"
sensitive = true
}
output "db_password" {
value = var.db_password
sensitive = true
}
# Phase out a deprecated input step by step
variable "instance_type_legacy" {
type = string
default = null
description = "DEPRECATED: use compute.instance.type instead. Scheduled for removal in a future major release"
}
variable "compute" {
description = "Compute resource configuration (new schema)"
type = object({
instance = object({
type = string
size = optional(number, 1)
})
})
}
Associate
問題 1
When the same variable is supplied from multiple sources, which precedence (high → low) does Terraform use to pick the final value?
正解: A
Terraform gives CLI flags (-var / -var-file) the highest precedence, followed by tfvars (terraform.tfvars / *.auto.tfvars), then environment variables TF_VAR_*, with the variable block default last. This is the stable behavior described in the official documentation.
From which Terraform version are optional attributes on object types available?
Optional attributes and their default expressions inside object types are supported from Terraform 1.3 onward. For modules that span versions, either declare a minimum with required_version or fall back to filling in values through locals as before.
If I set sensitive=true on a variable, is the value fully hidden?
No. sensitive only masks the value in plan output and UI displays. The value is still stored in state, so you need backend encryption (for example Terraform Cloud/Enterprise, or S3 + KMS) together with strict access control. Once the value is passed to an external service, you also need to watch downstream logs.
How should I choose between default and null, and how do I set nullable?
If you want the module to run on a safe default when the input is omitted, set a safe value via default. If the value is required and you cannot accept it being missing, set nullable=false and force an explicit input. null represents an intentional absence, but leaving nullable=true makes it easy to miss an accidental null flowing through. For important parameters, prefer nullable=false.
Practice with certification-focused question sets
無料で問題を解いてみる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.
HCL Syntax: Terraform's Configuration Language (2026)
HCL2 fundamentals for Terraform — blocks, attributes, expres...
Terraform Authoring & Operations Pro: Complete Guide (2026)
Tactics for the Terraform Pro exam — module authoring, works...
Terraform Providers: Plugin Management Fundamentals (2026)
Provider mechanics — required_providers, versions, mirrors, ...
Terraform Resource Blocks: Declarative Infra Units (2026)
Resource block fundamentals — addresses, references, common ...
Terraform Data Sources: Read-Only External Data (2026)
Data source basics — declaration, refresh behavior, dependen...