What kinds of values can the source argument of a Terraform module block take, and how does Terraform decide how to fetch each one?
answer
- The shape of the string decides the fetcher
- ./ and ../ mean something specific
- Three slash-separated parts
- // selects a subdirectory
- Query parameters configure the fetcher
basics
~20 sTerraform accepts local paths beginning with ./ or ../, module registry addresses of the form namespace/name/provider, Git and Mercurial repositories, plain HTTP or S3/GCS archives. Terraform inspects the string's shape and prefix to pick the fetcher.
solid answer
~50 sThe `source` value is a module source address, and its shape tells Terraform which installer to use. A value starting with `./` or `../` is a local directory, read in place with no download. A three-part `namespace/name/provider` — optionally with a registry host in front, like `app.terraform.io/acme/vpc/aws` — is a registry module, and only these accept a `version` argument. A `git::` prefix over HTTPS or SSH, or the `github.com/org/repo` shorthand, clones a repository and takes `?ref=` to select a revision. There are also generic HTTP archive URLs and `s3::` / `gcs::` object-store sources. Two suffixes apply across the remote forms: `//subdir` selects a subdirectory inside the fetched package, and query parameters like `?ref=` configure the fetcher. In practice most teams use exactly two: local paths within a repo, and registry or Git for anything shared.
code
hcl · 21 linesmodule "local_network" {
source = "./modules/network"
}
module "registry_vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.1"
}
module "private_registry_vpc" {
source = "app.terraform.io/acme/vpc/aws"
version = "1.4.0"
}
module "git_vpc" {
source = "git::ssh://[email protected]/acme/terraform-modules.git//vpc?ref=v2.3.1"
}
module "archive_vpc" {
source = "s3::https://s3.eu-west-1.amazonaws.com/acme-modules/vpc-1.4.0.zip"
}go deeper
Be able to name the common forms — local path, registry, Git — and recognise which one a given source string is. Know that init is what actually downloads remote modules.
Explain what each form implies for versioning and authentication: registry gives you a constraint, Git gives you a ref and reuses your Git credentials, local gives you your own commit history.
Discuss which forms you allow in a real estate and why: what the CI runner can authenticate to, whether an air-gapped environment forces archive sources, and how you keep the set small enough to reason about.
Own the distribution decision for the organisation — whether internal modules are consumed from a registry, Git, or an artifact store — and the cost of supporting more than one path in every pipeline and every audit.
## One argument, several protocols `source` is the only required argument of a `module` block, and Terraform treats it as a *module source address* rather than a URL. At `terraform init` (or `terraform get`) the module installer inspects the string, picks a fetcher, downloads the module package if it is remote, and records the result under `.terraform/modules` in the working directory. Everything about versioning follows from which form you chose. ## Local paths ```hcl module "network" { source = "./modules/network" } ``` A source beginning with `./` or `../` is a local path. It is the one form that is *not* a module package: nothing is downloaded or copied, the directory is read where it sits, and it shares your repository's history — so a local module is versioned by your own commits and accepts neither `version` nor `?ref=`. A subtlety worth knowing: a relative path *inside* a downloaded remote module resolves relative to that module's package, not to your root module, which is why remote modules can safely contain their own `./modules/...` children. ## Registry addresses ```hcl module "vpc" { source = "terraform-aws-modules/vpc/aws" version = "~> 5.1" } ``` Three slash-separated parts mean the public Terraform Registry: namespace, module name, target provider. Prefixing a hostname — `app.terraform.io/acme/vpc/aws`, or your own host implementing the registry protocol — means a private registry, which is authenticated through the credentials Terraform holds for that host. This is the only form where the separate `version` argument applies, because a registry can be asked which versions exist. ## Git ```hcl module "vpc" { source = "git::https://github.com/acme/terraform-modules.git//vpc?ref=v2.3.1" } ``` The explicit `git::` prefix works over HTTPS or SSH (`git::ssh://git@host/org/repo.git`). Terraform also detects GitHub and Bitbucket shorthands — `github.com/acme/terraform-vpc` — and treats them as Git. Authentication is whatever your Git client already does: SSH agent keys, deploy keys, or a credential helper, which matters when the same configuration must initialise inside CI. Mercurial has an equivalent `hg::` prefix, rarely seen today. ## Archives and object stores A plain HTTP(S) URL pointing at a `.zip` is fetched and unpacked. `s3::https://…/module.zip` and `gcs::https://…/module.zip` fetch an object using the cloud provider's credentials. These forms have no built-in version concept, so versioning is done by putting the version into the object key (`modules/vpc/1.4.0.zip`). ## The two universal modifiers Across the remote forms: - `//subdir` selects a directory inside the fetched package. `git::https://host/repo.git//modules/vpc` uses only that folder, which is how one repository can hold many modules. - `?key=value` query parameters configure the fetcher; `?ref=` for Git is the one you will use daily. ## What init does with the result `terraform init` downloads every remote module into `.terraform/modules` and writes a `modules.json` manifest describing what was resolved. That directory is a cache, not a lock: it is gitignored, and a clean checkout re-resolves everything from the addresses in your code. This is the mechanical reason an unpinned remote source is a reproducibility problem — the code is the only durable record of what should be fetched. ## Choosing between the forms The honest short list for most teams is: local paths for modules that only this repository uses and that you want to change in the same commit as their caller; a registry (public for community modules, private for internal ones) when you want a version list, published docs, and constraint resolution; Git when you want zero extra infrastructure and are willing to pin and upgrade by hand. Archive sources show up mainly in air-gapped environments where an object store is the only reachable artifact host.
- What does the double slash in a module source address mean, and when do you need it?`//` separates the package from a subdirectory inside it: `git::https://host/repo.git//modules/vpc` fetches the repository but uses only `modules/vpc` as the module. You need it whenever one repository or archive contains several modules — a single slash would just be part of the path and Terraform would look for the module at the package root.
- Why does a local-path module take no version argument?A local path is not a module package — Terraform reads the directory in place instead of downloading anything, so there is no separate artifact to version. It is versioned by the same commit as the code that calls it, which is precisely the property that makes local modules convenient for changes that must land atomically with their caller.
saying these in an interview costs you the question
- Thinks source is always a plain URL
- Writes ./ paths as remote addresses or vice versa
- Believes any source form accepts version
- Treats //subdir as an ordinary path separator
- Assumes .terraform/modules should be committed