Terraform Modules · Topic 1 of 2

Module Sources and Variable Scope

Terraform Associate 004: module sources from local paths to registry, Git, HTTP and S3, how init installs them, and how inputs and outputs scope values.

  • 5 min
  • Includes a diagram
In a nutshell
  • The root module is where you run Terraform; modules it calls are child modules.
  • Local paths start with ./ or ../; registry sources use NAMESPACE/NAME/PROVIDER.
  • Git sources use ref for a branch, tag or commit, and // for a subdirectory.

What it is

A module is a set of Terraform files in one directory. The directory where you run Terraform is the root module; any module it calls with a module block is a child module.

The source argument tells Terraform where a child module's code lives. Variable scope describes which values a module can see: each module only sees its own variables, locals and resources.

module blocksource argumentterraform initdownloads module.terraform/moduleslocal copyPlanuses module code

How it works

Source types

SourceExample source value
Local path"./modules/network" or "../shared/vpc"
Public Terraform Registry"hashicorp/consul/aws"
Private registry"app.terraform.io/example-org/vpc/aws"
GitHub"github.com/example-org/terraform-aws-vpc"
Generic Git"git::https://example.com/vpc.git"
Bitbucket"bitbucket.org/example-org/terraform-aws-vpc"
HTTP URL"https://example.com/vpc-module.zip"
S3 bucket"s3::https://s3.amazonaws.com/example-bucket/vpc.zip"
GCS bucket"gcs::https://www.googleapis.com/storage/v1/example-bucket/vpc.zip"

A local path must start with ./ or ../. Without that prefix, Terraform reads the string as a registry address.

Registry addresses use the form NAMESPACE/NAME/PROVIDER. A private registry adds the hostname in front, as in HOSTNAME/NAMESPACE/NAME/PROVIDER.

Selecting a Git revision or subdirectory

For Git sources, the ref query parameter picks a branch, tag or commit. A double slash selects a subdirectory inside the repository or archive.

module "vpc" {
  source = "git::https://example.com/infra.git//modules/vpc?ref=v1.2.0"
}

The version argument does not work with Git sources; ref does that job. Page 2 of this chapter covers version for registry modules.

How Terraform installs modules

terraform init installs every module that the configuration calls. Remote modules are downloaded into the .terraform/modules directory; local path modules are used in place and not copied.

terraform init
terraform get

terraform get downloads modules only, without the rest of initialization, and terraform get -update refreshes modules already installed. When you add a module block or change its source, run terraform init again before planning.

Add or change sourceterraform initModule installedterraform plan

Variable scope

Module scope: the root module passes values to the child module only through input variables such as cidr_block, has no access to the child's locals or resources, and reads values back only through child outputs as module.network.subnet_id. A child module cannot read the caller's variables, locals or resources, and the caller cannot reach inside the child. There are no global variables.

DirectionMechanismReference
Parent to childArguments in the module block set the child's input variablesvar.NAME inside the child
Child to parentOutput blocks in the childmodule.MODULE_NAME.OUTPUT_NAME in the parent
Child to its own codeLocals and resourceslocal.NAME, TYPE.NAME
module "network" {
  source     = "./modules/network"
  cidr_block = "10.0.0.0/16"
}

resource "aws_instance" "web" {
  ami           = var.ami_id
  instance_type = "t3.micro"
  subnet_id     = module.network.subnet_id
}

Here cidr_block must match a variable "cidr_block" in the child, and subnet_id must be declared as an output in the child. Any child variable without a default must be set in the module block.

Variable names do not leak

Two modules can each declare a variable called region without conflict, because each name exists only in its own module. A value reaches a child only when the caller passes it explicitly, for example region = var.region.

Providers are the main exception to strict isolation: by default, a child module inherits the default provider configurations of its parent. The providers argument in a module block can pass a different, aliased configuration. Reusable modules should not declare their own provider blocks, because a module that does cannot be called with count, for_each or depends_on.

How the exam asks about this

  • "Terraform reports the module source modules/app as an invalid registry address." The local path needs a ./ prefix: ./modules/app.
  • "A child module needs the VPC ID that the root module creates. How does it receive it?" Pass it as an argument in the module block, matching an input variable in the child.
  • "How does the root module use the subnet ID a child creates?" The child declares an output, read as module.network.subnet_id.

What to remember for the exam

  • The root module is where you run Terraform; modules it calls are child modules.
  • Local paths start with ./ or ../; registry sources use NAMESPACE/NAME/PROVIDER.
  • Git sources use ref for a branch, tag or commit, and // for a subdirectory.
  • terraform init (or terraform get) installs remote modules into .terraform/modules.
  • Values enter a child only through input variables and leave only through outputs.
  • Child modules inherit the parent's default provider configurations unless providers says otherwise.
Watch out

A child module can never use var. to read a variable declared in the root module. If the child needs the value, declare a variable in the child and pass the value in the module block.

Common mistakes

Leaving out the ./ prefix. source = "modules/app" is treated as a registry address and fails. A local directory must be written as "./modules/app".

Forgetting to run init after a change. Adding a module or changing its source leaves the configuration uninstalled, so terraform plan fails with a message asking you to run terraform init.

Referring to a child's resource directly. module.network.aws_subnet.main.id is not valid in the parent. The child must expose the value as an output, which the parent reads as module.network.subnet_id.

Check yourself

  • Where does terraform init put downloaded remote modules?

    In the .terraform/modules directory of the working directory. Local path modules are used in place and are not copied.

  • How does a child module return a value to its caller?

    It declares an output block, and the caller reads it as module.MODULE_NAME.OUTPUT_NAME.

  • How do you select a tag of a module stored in Git?

    Add the ref query parameter to the source, for example ?ref=v1.2.0; the version argument does not apply to Git sources.

Topic 1 of 2 in Terraform Modules

Test yourself on Terraform Modules

The full HashiCorp Terraform Associate 004 question bank has 600 questions, with timed practice exams and an explanation for every answer.

This study guide is independent and is not affiliated with, endorsed by, or sponsored by HashiCorp. Vendor names, certification names and exam codes are trademarks of their respective owners and are used for identification only.