DevOps Peach

Advanced Terraform · Modules and Code Structure · Lesson 2 of 5

Module Sources, Versions and the Lock File

Modules come from local paths, the registry or Git; pin them with version constraints or refs, and commit .terraform.lock.hcl so every run uses the same provider builds.

Practice interactively →

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.

Practice interactively →