Study guide

10.1 How Terraform Sources Modules

HashiCorp Certified: Terraform Associate 004

Track your progress as you read

Finished sections get a check mark.

1 Introduction & Exam Overview

1.1 Current Exam Facts 1.2 Format, Delivery, Retakes, and Recertification 1.3 004 Changes, Terraform 1.12, and Study Plan

2 Infrastructure as Code with Terraform

2.1 What Infrastructure as Code Is 2.2 Advantages of IaC Patterns 2.3 Multi-Cloud, Hybrid, and Service-Agnostic Workflows

3 Terraform Fundamentals: Providers & State

3.1 Install and Version Terraform Providers 3.2 How Terraform Uses Providers 3.3 Configuration with Multiple Providers 3.4 How Terraform Uses and Manages State

4 Core Terraform Workflow: Init & Validate

4.1 The Terraform Write–Plan–Apply Workflow 4.2 Initialize a Working Directory 4.3 Validate a Configuration

5 Plan, Apply, Destroy, and Formatting

5.1 Generate and Review an Execution Plan 5.2 Apply Changes to Infrastructure 5.3 Destroy Terraform-Managed Infrastructure 5.4 Formatting and Style Adjustments

6 Resources, Data Sources, and References

6.1 Resource Blocks vs Data Blocks 6.2 Resource Attributes and Cross-Resource References 6.3 Resource Addressing and Interpolation Patterns

7 Variables, Outputs, and Complex Types

7.1 Input Variables, tfvars, and Precedence 7.2 Output Values 7.3 Complex Types: list, map, object, tuple, set

8 Dynamic Configuration and Dependencies

8.1 Expressions, count, for_each, and dynamic Blocks 8.2 Built-in Functions 8.3 Dependencies, depends_on, and Lifecycle Rules

9 Conditions, Secrets, and Sensitive Data

9.1 Custom Conditions: validation, precondition, postcondition, check 9.2 Sensitive Data Handling 9.3 Vault, Ephemeral Values, and Write-Only Arguments

10 Terraform Modules

10.1 How Terraform Sources Modules 10.2 Variable Scope within Modules 10.3 Using Modules in Configuration 10.4 Managing Module Versions

11 Terraform State Management

11.1 The Local Backend 11.2 State Locking 11.3 Remote State and the backend Block 11.4 Resource Drift and State Operations

12 Maintain Infrastructure with Terraform

12.1 Import Existing Infrastructure 12.2 Inspect State with the CLI 12.3 Verbose Logging and Diagnostics

13 HCP Terraform

13.1 Create Infrastructure with HCP Terraform 13.2 Collaboration and Governance Features 13.3 Workspaces and Projects 13.4 HCP Terraform Integrations

10.1 How Terraform Sources Modules

Key Takeaways Explain This Topic AI

A module block's required source argument is the address Terraform uses during terraform init to locate the child's .tf files

Relative local paths must start with ./ or ../ and are used in place; absolute paths are copied into the module cache and are not recommended

Public registry modules use NAMESPACE/NAME/PROVIDER (hashicorp/consul/aws); HCP private modules prepend app.terraform.io/

Git modules use git:: plus a Git URL and pin a revision with ?ref=; HTTP, s3::, and gcs:: install from archives or terraform-get redirects

terraform init (or terraform get) installs remote modules into .terraform/modules; changing source or adding a module requires a new init

Last updated: August 2026

10.1 How Terraform Sources Modules

Quick Answer: The required source argument is the address Terraform uses to find a child module. Relative local paths start with ./ or ../ and are used in place. The public registry uses NAMESPACE/NAME/PROVIDER ( hashicorp/consul/aws ). Git uses git::https://... and pins a revision with ?ref= . HTTP, s3:: , gcs:: , and the HCP private registry ( app.terraform.io/... ) are the other sources 004 tests. terraform init installs remote modules into .terraform/modules .

Objective 5a on Terraform Associate (004) is: explain how Terraform sources modules. The product version on the exam is Terraform 1.12 . Official pages: Module sources , the module block reference , and Use modules in your configuration .

Why source is a separate idea from version

A module is a directory of Terraform configuration. The root module is the working directory where you run Terraform. A child module is any directory you call with a module block. source answers one question: where are that child's files? version (section 10.4) answers a different question: which published edition of those files? Mixing the two is the most common 5a miss.

On Terraform 1.12, write source as a static address in the module block. After you add a module, change source , or change a registry version , you must run terraform init again so Terraform can refresh the local install. module "vpc" {

  • source = "./modules/vpc"
  • }
  • module "consul" {
  • source = "hashicorp/consul/aws"
  • version = "0.11.0"
  • }
  • module "shared_net" {
  • source = "git::https://example.com/network.git//modules/vpc?ref=v1.2.0"
  • }

You may point two module blocks at the same source if their labels differ. That is how one VPC module becomes module.network_prod and module.network_lab with different inputs.

Source types 004 expects you to recognize

Source Address shape How Terraform installs it version argument?

  • Relative local ./modules/vpc , ../shared Used in place ; not copied as a package No
  • Absolute local /opt/modules/vpc or a Windows drive path Copied into the module cache as a package No

Public registry hashicorp/consul/aws ( NAMESPACE/NAME/PROVIDER ) Registry protocol from registry.terraform.io Yes

  • HCP private registry app.terraform.io/<ORG>/<NAME>/<PROVIDER> Same protocol against HCP Terraform Yes
  • Terraform Enterprise <HOSTNAME>/<ORG>/<NAME>/<PROVIDER> Same protocol against your TFE host Yes
  • Generic Git git::https://... or git::ssh://... git clone No — pin with ?ref=
  • GitHub shorthand github.com/<ORG>/<REPO> git clone over HTTPS No — ?ref=
  • HTTP / HTTPS Archive URL or a vanity URL GET, possibly following terraform-get No
  • S3 archive s3::https://s3-eu-west-1.amazonaws.com/bucket/vpc.zip Download and extract the archive No

GCS archive gcs::https://www.googleapis.com/storage/v1/BUCKET/path/module.zip Download and extract the archive No

Local paths

A local source must begin with ./ or ../ . That prefix is how Terraform distinguishes a disk path from a registry address. source = "modules/vpc" is not a local path — Terraform treats it as a registry-style address and init fails.

Relative local modules are special. Terraform records them in .terraform/modules (look at modules.json ) but does not copy their files into a package. The child is the directory on disk. Edit ./modules/vpc/main.tf and the next terraform plan sees the change without another init . That is the opposite of a registry or Git module, whose files live as a downloaded snapshot until you re-init or upgrade.

Absolute paths ( /usr/local/modules/vpc ) are recognized as local, but HashiCorp does not recommend them. Terraform copies an absolute path into the module cache as a package, which couples the configuration to one machine's filesystem layout.

../shared is legal and common when several root modules in a monorepo share a sibling directory. The path is resolved relative to the calling module , not relative to wherever your shell happens to sit.

Terraform Registry and HCP

The public registry address has three slash-separated segments and no https:// :

Namespace — the publisher ( hashicorp , terraform-aws-modules , your org).

Name — the module name ( consul , vpc ).

Provider — the primary provider the module targets ( aws , azurerm , google ).

hashicorp/consul/aws is the official example on HashiCorp's module-source documentation. For a private module on HCP Terraform, prepend the hostname: app.terraform.io/acme/vpc/aws . Terraform Enterprise uses your deployment hostname the same way. The registry protocol is the only source type that fully supports the version argument.

Git, including ref

Prefix a Git URL with git:: so Terraform knows to run git clone . Pin the revision with the ref query parameter. ref accepts anything git checkout accepts: a tag ( v1.2.0 ), a branch ( main ), or a commit SHA. If you omit ref , Terraform clones the default branch pointed to by HEAD — that is not a production pin. module "vpc" {

  • source = "git::https://example.com/vpc.git?ref=v1.2.0"
  • }
  • module "storage" {
  • source = "git::https://example.com/storage.git?ref=51d462976d84fdea54b47d80dcabbf680badcdb8"
  • }
  • module "private" {
  • source = "git::ssh://git@github.com/acme/network.git//modules/vpc?ref=v1.4.0"
  • }

GitHub also has a shorthand: source = "github.com/hashicorp/example" . Terraform still runs git clone . SSH keys are the usual way to clone private repositories from automation and from HCP Terraform. You cannot put version = "1.2.0" on a Git source and expect the registry selector to run — Git is not a registry.

A module can live in a subdirectory of a larger repository. Insert // after the repository (the package root) and before the inner path. Query parameters such as ref go after the subdirectory: git::https://example.com/network.git//modules/vpc?ref=v1.2.0 . Terraform clones the whole package, then reads the module from that subdirectory so sibling local source = "../iam" references inside the package still resolve.

HTTP, S3, and GCS

An HTTPS URL can point at an archive ( .zip , .tar.gz , .tgz , .tar.bz2 , .tar.xz ). Terraform downloads and extracts it. A vanity URL that is not an archive is fetched with GET plus terraform-get=1 . The server returns the real source either in an X-Terraform-Get header or in an HTML <meta name="terraform-get"> tag.

Object storage uses an explicit prefix so Terraform does not treat the URL as a generic HTTP redirect:

S3: s3::https://s3-eu-west-1.amazonaws.com/examplecorp-terraform-modules/vpc.zip . The object must be an archive. Credentials come from AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY , the default shared-credentials profile, or an EC2 instance profile. Buckets in us-east-1 use the s3.amazonaws.com hostname.

GCS: gcs::https://www.googleapis.com/storage/v1/BUCKET_NAME/PATH/module.zip . Authentication uses Application Default Credentials ( GOOGLE_APPLICATION_CREDENTIALS , GOOGLE_OAUTH_ACCESS_TOKEN , a GCE instance service account, or gcloud auth application-default login ).

What terraform init puts on disk

terraform init walks every module block, retrieves remote sources, and records the result under .terraform/modules . terraform get performs the module-install step alone. Do not commit .terraform/ . A teammate or an HCP Terraform run performs its own install from the source (and version / ref ) written in configuration.

Re-running init after modules are already installed adds newly declared modules but does not refresh already-installed remote modules. Use terraform init -upgrade or terraform get -update when you intend to pull newer matching code. Changing source always requires a new init.

004 traps for module sources

./ and ../ are required for local paths. Bare modules/vpc is not local.

Relative local modules are live directories. Registry and Git modules are snapshots.

hashicorp/consul/aws is a registry address, not a GitHub URL and not an S3 key.

version is registry-only. Git pins with ?ref= .

// marks a subdirectory inside a package; ?ref= comes after it.

HCP private modules start with app.terraform.io/ , not hashicorp/ .

terraform init is what downloads modules. terraform apply does not clone a new source by itself.

Loading diagram... How terraform init resolves module.source on Terraform 1.12