Objective 5: Terraform Modules
Everything you need to know about sourcing, using, scoping, and versioning modules for the Terraform Associate 004 exam.
5a — Module Sources
Registry, local path, GitHub, generic Git, HCP Terraform private registry, S3/GCS/HTTP. terraform init downloads into .terraform/modules/.
5b — Variable Scope
Root vs. child modules. Input variables, output values, and local values. Locals are module-private; outputs expose data to the caller.
5c — Using Modules
The module block, required vs. optional arguments, accessing outputs via module.name.output, and module composition (nested modules).
5d — Module Versions
Only registry and HCP private registry support version =. Git uses ?ref=. Pin versions in production; use terraform init -upgrade.
Exam Objectives Covered
- 5a — Explain how Terraform finds and fetches modules
- 5b — Explain the scope of values (variables, locals, outputs) within modules
- 5c — Demonstrate how to use modules from the registry and configure their inputs
- 5d — Apply version constraints to registry modules
Core Concepts
Deep-dive reference for each exam objective. Study all four sections before attempting the quiz.
📦 Module Sources 5a
The source argument in a module block tells Terraform where to find the module's code. terraform init downloads remote sources into .terraform/modules/ (gitignore this folder).
| Source Type | Example | version = |
|---|---|---|
| Terraform Registry | hashicorp/consul/aws |
Yes |
| HCP Terraform Private Registry | app.terraform.io/example_corp/vpc/aws |
Yes |
| Local path | ./modules/networking |
No |
| GitHub | github.com/hashicorp/example |
No (use ?ref=) |
| Generic Git | git::https://example.com/vpc.git?ref=v1.2 |
No (use ?ref=) |
| Git over SSH | git::ssh://user@example.com/storage.git?ref=v1.2.0 |
No (use ?ref=) |
| S3 / GCS / HTTP | https://example.com/vpc-module.zip |
No |
- Registry URL:
registry.terraform.io— official and verified modules live here - Registry format:
<namespace>/<module>/<provider>— three parts, always - Verified modules show a blue checkmark; maintained by known vendors
- Publishable module naming convention:
terraform-<provider>-<name> - Local path modules: must begin with
./or../so Terraform distinguishes them from registry sources - After adding or changing module source: always re-run
terraform init
🔀 Variable Scope Within Modules 5b
Terraform organises configuration into a tree of modules. Understanding scope determines what values each module can see and expose.
- Root module: the working directory where you run
terraformcommands; all.tffiles at that level - Child module: any module called via a
moduleblock from the root or another module - Input variables: declared with
variableblocks in the child; the caller passes values as arguments in themoduleblock - Output values: declared with
outputin the child module; the only way to expose data back to the caller - Local values:
locals { }— computed, named values that are module-private; they never escape their module - A parent module cannot directly reference a child module's resources or variables — only its outputs
- Accessing a child module's output:
module.<name>.<output_name>
⚙️ Using Modules in Configuration 5c
The module block is how you call a module and pass input values. Every argument in the block maps to an input variable in the child module.
- Required arguments: any input variable in the child that has no default value must be provided by the caller
- Optional arguments: variables with defaults can be omitted; child uses its default
- After adding or changing a
source: must re-runterraform initto download the module - Module composition: modules can call other modules — this creates a tree (nested/child-of-child); root is always at the top
- Standard module file structure:
main.tf,variables.tf,outputs.tf,versions.tf,README.md - Any directory containing
.tffiles is technically a module
🔢 Module Versions 5d
The version argument is only supported for Terraform Registry and HCP Terraform Private Registry sources. All other sources use different pinning mechanisms.
- Registry only: use
version = "~> 5.0"in themoduleblock - Git sources: pin with
?ref=v1.2.0appended to the URL — noversionargument - Local paths: no versioning — they always read the current directory contents
- Version constraint syntax is identical to provider version constraints
~> 5.0allows5.xbut not6.0(pessimistic constraint operator)>= 4.0, < 6.0— range constraint"= 5.1.2"or just"5.1.2"— exact pin, best for production- After changing a module version: run
terraform init -upgradeto download the new version - Best practice: always pin exact versions in production; use
~>for minor version flexibility in development
Memory Hooks
Six high-retention mnemonics for the most exam-tested module concepts.
version argument. Git sources pin with ?ref= in the URL instead.locals { } values are completely private to the module where they are defined. They cannot be referenced from a parent or sibling module — ever.output. The caller accesses it as module.name.output_name — no shortcut exists.terraform init fetches remote modules into .terraform/modules/. This folder is auto-generated — always add it to .gitignore.Practice Quiz
10 questions covering all module objectives. Select an answer to see instant feedback.
vpc module in the aws provider namespace by terraform-aws-modules?module block that sources from GitHub. What must you do before running terraform plan?v2.1.0. Which syntax is correct?locals { region = "us-east-1" }. How can the parent module access this value?network has an output named subnet_ids. How does the root module reference it?5.1, 5.9, and 5.99 but NOT 6.0?version constraint from ~> 4.0 to ~> 5.0. What command downloads the new version?version = "3.0.0" to a module block that uses a local path source (./modules/vpc). What happens?Score: 0 / 10
Flashcards
Click any card to reveal the answer. Work through all 8 for full objective coverage.
version = argument?version argument (Git uses ?ref= instead).vpc_id from a child module called network?module.network.vpc_id. The child must declare the output with an output block; there is no other way to expose data.terraform commands. A child module is any module invoked via a module block from the root or from another module.locals value defined in a child module? Why or why not?locals { }) are scoped exclusively to the module where they are defined. They are completely private and never exposed outside that module.source or adding a new module block?terraform init again. Terraform downloads the new module source into .terraform/modules/. For a version upgrade specifically, use terraform init -upgrade.~> pessimistic constraint operator do when applied to a module version like ~> 5.0?>= 5.0 and < 6.0 (increments only the rightmost component). It permits patch and minor updates within the 5.x line but blocks the next major version.main.tf (resources), variables.tf (input declarations), outputs.tf (output declarations), versions.tf (required_providers), and README.md (required for published modules).Tap a card to flip it · Tap again to return
Study Advisor
Select a category to get targeted study tips and common exam traps.
📦 Module Sources — Study Tips
- Registry format quiz trap: It is always
namespace/module/provider. The exam may reverse the order or use only two parts — watch for this. - Local path prefix: Local paths MUST begin with
./or../. Without the dot-slash, Terraform treats the string as a registry address. - Git vs GitHub: You can use
github.com/org/reposhorthand or the fullgit::https://...form. Both are valid on the exam. - Download location: All remote modules are cached in
.terraform/modules/. Always add this to.gitignore. - Verified modules: Look for the blue checkmark on
registry.terraform.io. Verified ≠ official HashiCorp — it means a vetted vendor. - Naming convention: Publishable modules must follow
terraform-<provider>-<name>for the repository name.
🔢 Versioning — Study Tips
- version = is registry-exclusive: The most common exam trap —
versionis NOT supported for local paths or any Git-based source. Only registry and HCP private registry. - Git pinning: For Git, version pinning is done via the source URL itself: append
?ref=v1.2.0to the URL. - ~> operator:
~> 5.0allows 5.x but not 6.0.~> 5.1allows 5.1.x but not 5.2. The rightmost digit is the one that can increment. - Init -upgrade: Changing a version constraint alone won't download the new version. You must run
terraform init -upgrade. - Production best practice: Exact pins (
"5.1.2") for production; pessimistic constraints (~> 5.0) for development flexibility.
📤 Inputs & Outputs — Study Tips
- Locals never escape:
locals { }values are strictly module-private. No reference likemodule.child.local.nameexists — it will error. - Only outputs expose data: To pass data from child to parent, the child must declare an
outputblock. There is no other mechanism. - Output reference syntax:
module.<label>.<output_name>— note three parts: keyword, module label, output name. - Required vs optional inputs: Variables with no
defaultare required; the caller must supply them. Variables with adefaultare optional. - Sensitive outputs: Mark outputs
sensitive = trueto suppress them in plan/apply output, but they are still accessible by reference.
🏗️ Module Structure — Study Tips
- Any directory is a module: Technically, any directory with
.tffiles is a module. The formal five-file structure is a best practice, not a requirement. - Root module definition: The root module is the working directory from which you run Terraform — not the first
moduleblock in the code. - Child module isolation: A parent cannot directly reference resources or variables inside a child module — only its declared outputs.
- versions.tf: Contains the
terraform { required_providers { } }block — separating this makes the module easier to maintain. - README required: Published modules on the registry must include a README.md. Input/output blocks should have
descriptionfields for the registry to display documentation.
🔄 Module Workflow — Study Tips
- Always init after source changes: Any time you add a new module block or change the
source, you must runterraform initbeforeplanorapply. - Init -upgrade for version changes: If you only change the
versionconstraint, useterraform init -upgradeto fetch the updated version. - Module composition: Modules can call other modules, creating a tree. The root is always the top-level working directory. This is called module composition, not inheritance.
- Gitignore .terraform/modules/: This directory is auto-generated by
terraform initand should never be committed to source control. - Local modules and init: Local path modules do not need to be "downloaded," but you still need to run
terraform initafter adding them so Terraform registers the module in its internal index.
Resources
Authoritative references for deeper study and exam preparation.
Terraform Modules Documentation
Complete language reference for module blocks, sources, inputs, outputs, and composition.
Terraform Registry
Browse verified and community modules. See version history, changelogs, and input/output docs.
Terraform Associate 004 Exam Guide
Official objectives, study guide, and sample questions from HashiCorp.
Create a Module Tutorial
Step-by-step tutorial for building, structuring, and publishing your own Terraform module.
Use Registry Modules Tutorial
Hands-on guide to sourcing, configuring, and versioning modules from the public registry.
FlashGenius Practice Tests
Full-length Terraform Associate 004 practice exams with detailed answer explanations.