A module source tells Terraform where the code lives. Provider and module versions are pinned separately, and the dependency lock file records the exact provider builds that were chosen.
flowchart LR C["module block"] --> L["Local path ../modules/app"] C --> REG["Registry name + version"] C --> G["Git URL ?ref=v1.4.0"] P["required_providers"] --> LK[".terraform.lock.hcl"] LK --> I["terraform init"] %% step C: The source argument picks one of several installers; it cannot use variables. %% step L: Local paths start with ./ or ../ and are read in place, never versioned separately. %% step REG: Registry modules use namespace/name/provider and accept a version constraint. %% step G: Git sources pin a tag or commit with ?ref=; the version argument does not apply. %% step P: required_providers sets the allowed provider versions for this configuration. %% step LK: The lock file records the chosen provider version and checksums; commit it. %% step I: init reads the lock file and installs exactly what it lists.
Source types
| Source | Example | Versioning |
|---|---|---|
| Local path | source = "../modules/app" |
None; same repo, same commit |
| Terraform Registry | source = "terraform-aws-modules/vpc/aws" + version = "~> 5.8" |
version constraint |
| Git | source = "git::https://github.com/example-org/tf-modules.git//app?ref=v1.4.0" |
?ref= tag, branch or SHA |
| Private registry | source = "app.terraform.io/example-org/app/docker" |
version constraint |
//app selects a sub-folder of the repository. source and version must be literal values: no variables.
Version constraints
| Constraint | Allows |
|---|---|
= 1.4.0 |
Exactly 1.4.0 |
>= 1.4.0, < 2.0.0 |
Any 1.x from 1.4.0 |
~> 1.4 |
1.4 or newer, below 2.0 |
~> 1.4.0 |
1.4.x patch releases only |
Reusable modules should declare a minimum (>= 3.0) so callers have room; root modules pin tighter (~> 3.6).
terraform {
required_version = ">= 1.10"
required_providers {
docker = { source = "kreuzwerker/docker", version = "~> 3.6" }
}
}
The lock file
terraform init writes .terraform.lock.hcl with the selected version of every provider and its checksums. Commit it: teammates and CI then install the same build, and a new provider release cannot sneak into a run. It does not lock module versions; pin those in the module block.
| Command | Meaning |
|---|---|
terraform init -upgrade |
Pick the newest versions allowed by the constraints and rewrite the lock file |
terraform providers lock -platform=linux_amd64 -platform=darwin_arm64 |
Add checksums for other operating systems (CI on Linux, laptops on macOS) |
terraform providers |
Show which module requires which provider |
Traps: deleting the lock file to "fix" an error silently upgrades providers; a branch ref such as ?ref=main is not a pin, because the branch moves.