All guides
Practice now
D5 · Terraform Modules

Module sources and the Terraform Registry

Terraform supports multiple types of sources for : local, the public registry, GitHub, S3, and more. Knowing the exact format of each source and when to pin versions is a frequent exam topic.

Module source types

TypePrefix / FormatRequires version?When to use it
Local"./modules/vpc"❌ NoModules from the same repository
Public registry"hashicorp/consul/aws"✅ YesVerified public modules
"app.terraform.io/org/vpc/aws"✅ YesPrivate corporate modules
GitHub HTTPS"github.com/org/repo"❌ No*Modules in public or private GitHub repos
GitHub SSH"git@github.com:org/repo"❌ No*GitHub repos via SSH (no token)
Generic Git"git::https://..."❌ No*Any Git server
S3"s3::https://bucket/module.zip"❌ NoModules packaged in S3
GCS"gcs::https://bucket/module.zip"❌ NoModules packaged in GCS

* For Git sources, the ref argument is used in the URL (e.g. ?ref=v2.0.0) instead of version.

Local sources

Local sources reference a directory on the same filesystem. They use relative paths (starting with ./ or ../).

# Module in a subdirectory of the same repository
module "vpc" {
  source = "./modules/vpc"       # relative to the calling module
}

# Module in a parent directory
module "shared" {
  source = "../shared/modules/tags"
}

# version cannot be used with local sources:
# version = "~> 1.0"  ← ERROR with local sources

# terraform get / terraform init — both copy the module
# into .terraform/modules/ of the working directory

💡 Relative vs absolute paths

Always use relative paths (./ or ../) for local modules. Absolute paths (/home/user/modules) work on your machine but break in CI/CD or on other machines. The path is relative to the file making the call, not to the directory where you run Terraform.

Public and private registry

# PUBLIC registry: namespace/name/provider
# (Terraform assumes registry.terraform.io as the host)
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 5.0"            # REQUIRED for the registry
}

module "iam_role" {
  source  = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks"
  version = "~> 5.30"
  # The double // lets you reference a submodule within the repository
}

# PRIVATE HCP Terraform registry: app.terraform.io/org/name/provider
module "vpc" {
  source  = "app.terraform.io/my-company/vpc/aws"
  version = "~> 2.0"
}

# Also works with your Terraform Enterprise host:
module "vpc" {
  source  = "terraform.my-company.com/org/vpc/aws"
  version = "~> 1.0"
}

🎯 Submodules with //

The double slash // in the source separates the repository from the subdirectory within it. For example: terraform-aws-modules/iam/aws//modules/iam-role references the modules/iam-role subdirectory inside the terraform-aws-modules/iam/aws module.

Git sources (GitHub, GitLab)

# GitHub HTTPS (for public repos or with an HTTPS token)
module "vpc" {
  source = "github.com/my-org/terraform-vpc-module"
}

# GitHub HTTPS with ref (tag, branch or commit)
module "vpc" {
  source = "github.com/my-org/terraform-vpc-module?ref=v2.0.0"
}

# GitHub SSH (authentication via SSH key)
module "vpc" {
  source = "git@github.com:my-org/terraform-vpc-module.git"
}

# Generic Git (any Git server)
module "vpc" {
  source = "git::https://gitlab.my-company.com/infra/vpc.git?ref=v1.5"
}

# Submodule within the repo (with //)
module "vpc" {
  source = "github.com/my-org/terraform-modules//modules/vpc?ref=v3.0"
}
Git sourceAuthenticationref to pin a version
github.com/org/repoHTTPS token in an environment variable or ~/.netrc?ref=v2.0.0 or ?ref=main
git@github.com:org/repoSSH key in ~/.ssh/?ref=v2.0.0
git::https://gitlab.com/...Token in the URL or ~/.netrc?ref=tag-or-commit
bitbucket.org/org/repoHTTPS token?ref=v1.0

S3, GCS and other sources

# Amazon S3 (module packaged as .zip or .tar.gz)
module "vpc" {
  source = "s3::https://s3-eu-west-1.amazonaws.com/my-bucket/vpc-module.zip"
}

# Google Cloud Storage
module "vpc" {
  source = "gcs::https://www.googleapis.com/storage/v1/my-bucket/vpc-module.zip"
}

# Generic HTTP/HTTPS (any URL that returns a file)
module "vpc" {
  source = "https://my-server.com/modules/vpc-1.0.0.zip"
}

# Note: these sources do not support the version argument
# The version is managed in the file name or the URL

When and how to pin versions

Source typePin version?How
Public/private registry✅ ALWAYS — required for the registryversion = "~> X.Y" argument
Local source❌ Not applicableIt has no versions, it is the local code
Git with a tag✅ Recommended — use a semantic tag?ref=v2.0.0 in the URL
Git with a branch⚠️ Not recommended in prod?ref=main — can change at any time
S3/GCS/HTTP✅ Recommended — in the file nameURL includes the version: module-v2.0.0.zip

⚠️ No version on the registry = risk of breakage

If you do not specify version for a registry module, Terraform will install the latest available version on every terraform init. A new major version of the module could introduce breaking changes that break your configuration without warning.

Did you get this topic?

Test what you just learned

A team wants to use a Terraform module stored in a private company GitHub repository. They need exactly the version tagged 'v3.2.1' to always be used. What is the correct way to configure the source?

Ready to test "Module Sources and the Terraform Registry"?

Put what you learned to the test with original Terraform Associate exam questions.

Start Terraform Associate mock exam

Unlimited mock exams with the Pro plan

Log in to track your progress.