linuxlab.io
Tutorials▾
  • Linux & networking
    File system, processes, TCP/IP, BGP and OSPF
    →
  • Terraform & IaC
    HCL, state, plan/apply on a LocalStack sandbox
    →
  • Git & GitHub
    Object model, plumbing, branching, GitHub Actions
    →
All tutorials →
PricingAboutSign inCreate account
/
Intro
Lessons
Footer
linuxlab-TutorialsPricingAboutPrivacy & cookies
Copyright © 2026 LinuxLab. All rights reserved.
linuxlab.io
Tutorials▾
  • Linux & networking
    File system, processes, TCP/IP, BGP and OSPF
    →
  • Terraform & IaC
    HCL, state, plan/apply on a LocalStack sandbox
    →
  • Git & GitHub
    Object model, plumbing, branching, GitHub Actions
    →
All tutorials →
PricingAboutSign inCreate account
/
  • Introduction
  • Lessons
  • How it works
  • Knowledge base
  • Cheat sheet
  • Capstone
  • Interview prep
home/terraform/kb/Modules/tf-module-sources

kb/modules ── Modules ── intermediate

Module sources: local, git, registry, archive, S3

`source` tells Terraform where to fetch a module's code. There are five main types: a local path (`./modules/x`), a git repo (`git::https://...`), the Terraform Registry (`hashicorp/consul/aws`), an archive (`https://.../v1.0.zip`), and an S3 object (`s3::https://...`). Use local for code in your own repo, the Registry for public modules, and git for private ones.

view as markdownaka: terraform-module-source, terraform-module-sources

The five source types

TypeExampleWhen
Local./modules/s3-bucketThe module lives in this same repo
Terraform Registryterraform-aws-modules/vpc/awsA public module with semver versioning
Gitgit::https://github.com/org/repo.git//modules/x?ref=v1.2.3A private module in git
HTTP archivehttps://example.com/modules/x-v1.zip//xA CDN or artifact store
S3s3::https://s3.amazonaws.com/bucket/x.zipAn internal S3 bucket used as a registry

The type comes from the shape of the string, not from a separate field. Terraform infers it for you.

Local

hcl
module "logs" {
  source = "./modules/s3-bucket"
  name   = "..."
}
  • The path is relative to the .tf file that holds the module block.
  • terraform init creates a symlink under .terraform/modules/logs/.
  • Any edit to the module's HCL shows up immediately. These are just files in the repo.
  • No versioning. The module is part of your repo, so its version is whatever git HEAD points to.

Use this for modules that live alongside the root. Do not reach into a "neighboring" project through a local path, or you will spend your time fighting relative paths.

Terraform Registry

hcl
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "5.7.0"
  name = "main"
  cidr = "10.0.0.0/16"
  # ...
}

Format: <namespace>/<name>/<provider>. Three segments, no https://.

  • The registry is registry.terraform.io (public) or a private one (HCP Terraform, Spacelift, and so on).
  • version is a semver constraint: "5.7.0", "~> 5.7", ">= 5.0, < 6.0". See tf-module-versioning.
  • Leave out version and you get the latest. In CI that is a time bomb: tomorrow 6.0 ships with breaking changes, and the build breaks without a single commit on your side. Always pin version.

The public AWS modules (terraform-aws-modules/vpc/aws, .../eks/aws, .../alb/aws) are the de facto standard. They are large and parameterized for every edge case. They fit when your requirements match the standard. When they do not, writing your own module is easier than figuring out which of 40 inputs to flip.

Git

hcl
# SSH
module "billing" {
  source = "git::ssh://git@github.com/myorg/terraform-modules.git//billing?ref=v1.2.3"
}
# HTTPS
module "billing" {
  source = "git::https://github.com/myorg/terraform-modules.git//billing?ref=v1.2.3"
}

Format: git::<git-url>//<subpath>?ref=<branch_tag_or_sha>.

  • // before the subpath is a Terraform separator, not part of the git URL.
  • ref is a branch, tag, or commit SHA. For reproducibility, use only a tag or SHA, never a branch. Otherwise init pulls one commit today and a different one tomorrow.
  • Authentication is standard git: an SSH key, HTTPS basic auth, or a token through GIT_TERMINAL_PROMPT or a credential helper.
  • Corporate setups usually keep one git repo with a subdirectory per module. That makes versioning easy: a single tag v1.2.3 pins everything at once.

HTTP archive

hcl
module "x" {
  source = "https://example.com/modules/x-v1.0.tar.gz//x"
}

Terraform downloads the archive, unpacks it, and descends into the //x subpath. Supported formats: .zip, .tar.gz, .tar.bz2, .tar.xz.

This is handy when modules are published to a CDN or an artifact store (Artifactory, Nexus). Versioning happens through the file name or an HTTP header.

S3

hcl
module "x" {
  source = "s3::https://s3-eu-west-1.amazonaws.com/my-bucket/modules/x-v1.zip"
}

The s3:: prefix tells Terraform to use AWS credentials for access, from the same credentials chain as the AWS provider.

This is a middle ground between running your own git and running your own registry: cheap, private, and no extra service to operate. The downside is that there is no built-in semver, so you version by hand through file names.

Caching and init

On terraform init, Terraform downloads every module into .terraform/modules/:

.terraform/modules/
├── modules.json     # mapping: name in HCL → path
├── vpc/             # downloaded code for the vpc module
└── billing/         # downloaded code for the billing module

This folder is a cache. Edit a local module's HCL and the change is visible right away. Change source = "git::...?ref=v2.0.0", though, and you need terraform init -upgrade. Otherwise Terraform keeps using the old cached copy.

Gotchas

  • You cannot interpolate source. No source = "git::...?ref=${var.v}". Source is resolved during init, before variables exist. A module's version is a static value. If you need to "switch versions dynamically", keep them in separate root modules or use Terragrunt.

  • A branch instead of a tag is a landmine. ?ref=main means "whatever main is right now". Tomorrow someone merges a breaking change into main, your terraform init pulls it, and plan shows a destroy plus create. Always use a tag or SHA.

  • Registry modules can pull in other registry modules. Large AWS modules use dozens of subdependencies internally. One module "vpc" makes .terraform/modules/ swell to 30+ modules. That is normal, not a bug.

  • source = "./modules/x" breaks after you move the repo around. Move the modules into a subfolder and the relative path is broken. A bulk grep across the repo fixes it, but it hurts. The deeper the structure, the more often you hit this.

  • Do not use HTTP without HTTPS. source = "http://..." disables the TLS check. That is an opening for a "swap the module on the build machine" attack.

  • Authenticate private git through a credential helper, not in the URL. Do not write git::https://user:token@github.com/..., or the token lands in logs and in .terraform/modules/modules.json. Use git config --global credential.helper store locally and a secrets mechanism in CI.

§ команды

bash
terraform init

Download all modules into .terraform/modules/.

bash
terraform init -upgrade

Re-read source and download again. Needed after a version or ref change.

bash
cat .terraform/modules/modules.json | jq

Which modules are downloaded and where. Useful when debugging 'why is the wrong code being used'.

bash
terraform get -update

Modules only, no providers. A subcommand that init runs internally. Rarely needed by hand.

§ см. также

  • tf-module-basicsModule: a reusable piece of infrastructureA module is any directory that contains `.tf` files and can be referenced via a `module` block. The root module is the directory where you run terraform. Child modules are the ones being called. The module contract: input variables (what it accepts), output values (what it exposes). Everything else is an implementation detail.
  • tf-init-modulesHow terraform init pulls in modulesWhen your HCL has a module block, terraform init downloads the module source (from a registry, git, or a local folder) into .terraform/modules/. This course does not write modules; this article is an overview of how the mechanics work.
  • tf-lockfile.terraform.lock.hcl: pinning provider versionsThe lockfile pins the exact provider versions and their hashes, so you and CI always run the same build. It is created on terraform init and updated through init -upgrade. Commit it to git.
Footer
linuxlab-
Copyright © 2026 LinuxLab. All rights reserved.
Tutorials
Pricing
About
Privacy & cookies