Cloud  /  Terraform

IaC Terraform 50 guides · updated 2026

Infrastructure as code done right — providers, state, reusable modules, and the workflow patterns that keep multi-cloud deployments sane in 2026.

Terraform Module Versioning

Module versioning controls which version of a module a configuration uses. Pinning versions ensures that a configuration that works today still works in three months — when the module author releases a breaking change, your infrastructure isn’t affected until you consciously choose to upgrade.


Version Constraints

Version constraints in Terraform use the same operators as provider versioning:

# Exact version
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.1.2" # Exactly this version
}
# Minimum version
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = ">= 5.0.0" # 5.0.0 or newer
}
# Version range
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = ">= 5.0, < 6.0" # Any 5.x version
}
# Pessimistic constraint (most common)
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.1" # 5.1.0 to <6.0.0 (same minor, any patch)
}
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.1.2" # 5.1.2 to <5.2.0 (same minor.patch, any patch)
}

Semantic Versioning for Modules

Module versions follow semver (MAJOR.MINOR.PATCH):

Version bumpMeaningSafe to auto-upgrade?
Patch (5.1.1 → 5.1.2)Bug fix, no API changeYes
Minor (5.1.x → 5.2.0)New features, backwards compatibleUsually yes
Major (5.x.x → 6.0.0)Breaking changes to variables/outputsNo — review first

The ~> constraint lets you auto-upgrade patches (or patches+minors) while blocking major version changes:

# Auto-upgrade within 5.x.x, never upgrade to 6.x.x
version = "~> 5.1"
# Auto-upgrade within 5.1.x, never upgrade to 5.2.0
version = "~> 5.1.2"

Module Lock Files

When you run terraform init, Terraform resolves the exact version that satisfies your constraint and records it in .terraform.lock.hcl:

# .terraform.lock.hcl (commit this to git!)
provider "registry.terraform.io/hashicorp/aws" {
version = "5.31.0"
constraints = "~> 5.0"
hashes = [
"h1:abc123...",
"zh:def456...",
]
}

Note: The lock file tracks provider versions. Module versions from the Registry are recorded in .terraform/modules/modules.json but this file is NOT committed (it’s in .gitignore). The version constraint in your .tf file is what’s “locked” for modules.


Registry Modules: Versioning in Practice

# Reference popular modules with pinned versions
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.1"
name = "production-vpc"
cidr = "10.0.0.0/16"
azs = ["us-east-1a", "us-east-1b", "us-east-1c"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]
}
module "eks" {
source = "terraform-aws-modules/eks/aws"
version = "~> 20.0"
cluster_name = "production"
cluster_version = "1.31"
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnets
}
module "rds" {
source = "terraform-aws-modules/rds/aws"
version = "~> 6.0"
identifier = "production-db"
engine = "postgres"
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnets
}

Git-Based Module Versioning

For modules hosted in git, use tags as versions:

# Specific git tag (most stable)
module "networking" {
source = "git::https://github.com/mycompany/terraform-modules.git//networking?ref=v2.1.0"
}
# Specific commit SHA (most reproducible)
module "networking" {
source = "git::https://github.com/mycompany/terraform-modules.git//networking?ref=abc1234567890"
}
# Branch reference (least stable — changes on every push)
module "networking" {
source = "git::https://github.com/mycompany/terraform-modules.git//networking?ref=main"
# Avoid in production — the branch moves
}

Git-based modules don’t support the version argument — the ref parameter in the source URL is the version pin.


Upgrading Module Versions

Terminal window
# See what modules are currently installed
cat .terraform/modules/modules.json | python3 -m json.tool
# To upgrade to a newer compatible version, just run init again
# (version constraint controls what's selected)
terraform init -upgrade
# After upgrading, always run plan to see what changes
terraform plan
# If a major version upgrade is required, update the constraint first:
# version = "~> 5.1" → version = "~> 6.0"
# Then check the changelog for breaking changes
# Then run init -upgrade + plan

Private Registry Module Versioning (HCP Terraform)

# Private registry modules use the same version constraints
module "ecs_service" {
source = "app.terraform.io/mycompany/ecs-service/aws"
version = "~> 3.0"
name = "payment-service"
image = var.image
cluster_id = module.cluster.id
}

Publishing a new version to the private registry:

Terminal window
# Module naming convention: terraform-<provider>-<name>
# e.g., repo name: terraform-aws-ecs-service
# Tag the release in git
git tag v3.1.0
git push origin v3.1.0
# HCP Terraform picks up the new tag and publishes it as version 3.1.0

Version Pinning Strategy

# PRODUCTION: Pin to specific minor version, allow patch upgrades
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.1" # 5.1.x — patches only
}
# STAGING: Allow minor upgrades to test before production
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.0" # Any 5.x.x
}
# DEVELOPMENT: Looser — test new major versions
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = ">= 5.0"
}

Upgrade workflow:

  1. Update version constraint in staging first
  2. Run terraform init -upgrade && terraform plan
  3. Review plan for unexpected changes
  4. Apply to staging and test
  5. Update production constraint
  6. Apply to production