In the previous subchapter, we insisted on "always pinning the version" of a module. Now we'll see why this is so important and how to do it when the module is yours and lives in a Git repository. Module versioning is what allows you to use them safely and predictably as a team, without an unexpected change breaking your infrastructure.

The Problem: A Module That Changes Under Your Feet

Imagine your company has a corporate-network module in a Git repository, used by ten projects. One day, someone improves the module and changes how it creates subnets. If all ten projects automatically used "the latest version" of the module, all would be affected by that change at once, without warning. The next terraform plan for each project would show unexpected—and possibly dangerous—changes.

Without versioning:
  Module changes ──► all 10 projects inherit the change INSTANTLY
                      (no control, no warning) ⚠️ dangerous

You need a way to say "I use this specific version of the module, and it won't change until I decide to update." That's what versions are for.

The Solution: Versioning with Git Tags

A Git tag is a named marker you put on a specific point in a repository's history. It's used to mark versions. The most widespread convention is semantic versioning: numbers like v1.4.2.

Module repository history:
  ... commits ...  ─► [tag v1.0.0]
  ... more commits ─► [tag v1.1.0]
  ... more commits ─► [tag v2.0.0]

Each tag is a stable "snapshot" of the module at a given moment. Once created, it does not change: v1.0.0 will always be exactly that code.

Semantic Versioning: What the Numbers Mean

The format vMAJOR.MINOR.PATCH (e.g., v2.3.1) communicates the type of change:

Part When it increases Means
MAJOR (2.x.x) Incompatible changes May break users; be careful when updating
MINOR (x.3.x) New compatible functionality Adds features without breaking existing ones
PATCH (x.x.1) Bug fixes Fixes issues, safe to update

So, at a glance, you know the risk of updating: going from v1.2.0 to v1.3.0 (minor change) is safe; moving to v2.0.0 (major change) may require adjustments.

How to Use a Specific Version

When referencing a module from a Git repository, you specify which version (tag) you want to use with ref:

module "network" {
  source = "git::https://github.com/my-company/modules.git//network?ref=v1.2.0"
  # ...                                                          ▲
  #                                            the version (tag) I use
}

And for Registry modules (subchapter 18.3), with the version argument:

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "5.0.0"      # pinned version
  # ...
}

In both cases, you tell Terraform: "use exactly this version." Even if the module evolves, your project will keep using the version you pinned, stable and predictable, until you decide to change the number.

Why This Changes Everything

With versioning, each project controls when to update:

With versioning:
  Module releases v2.0.0  ──► projects stay on v1.x (no changes)
  Project A decides to update to v2.0.0 when ready, tests it, and upgrades.
  Project B stays safely on v1.x until it suits them.

Advantages:

  • Stability: your infrastructure doesn't change unless you decide.
  • Controlled updates: you upgrade when you're ready, reviewing the plan (subchapter 12.5) and testing first.
  • Reproducibility: anyone running your code gets the same result, because the module version is pinned.
  • Safe teamwork: different projects use different versions without interfering with each other.

Real-world example: the platform team publishes corporate-network v2.0.0 with an important improvement that requires adjustments (major change). Instead of imposing it on everyone at once, each project team migrates to v2.0.0 when they can: they read the release notes, test in their development environment, review the plan, and then update production. No one is caught off guard. Versioning turns a potentially chaotic update into an orderly process.

The Connection with Team Workflow

This fits perfectly with what we saw in subchapter 12.5 (PR review of plans). Updating a module version is a code change that goes through a Pull Request: you change ref=v1.2.0 to ref=v2.0.0, CI shows the plan with what that implies, a teammate reviews it, and only then is it applied. Versioning + plan review = safe and traceable infrastructure changes.

What You Should Remember

  • Without versioning, a change in a shared module would instantly affect all projects using it, without control: dangerous.
  • A Git tag marks a stable and immutable version of the module. The convention is semantic versioning: vMAJOR.MINOR.PATCH.
  • The numbers communicate the risk: MAJOR = incompatible changes (be careful), MINOR = new compatible functionality, PATCH = safe fixes.
  • Pin the version with ref=vX.Y.Z (Git modules) or version = "X.Y.Z" (Registry): your project uses exactly that version until you decide to change it.
  • Benefits: stability, controlled updates, reproducibility, and safe teamwork. Fits with the PR review of plans workflow (subchapter 12.5).

In the last subchapter of the chapter, we'll look at an important design dilemma: when to create generic modules (highly reusable) versus domain-specific modules.

Cloud, AWS & Terraform — From Zero to Expert

Chapter 1 · What is cloud computing

Chapter 2 · The cloud market and major providers

Chapter 3 · Regions, availability zones and edge

Chapter 4 · Compute: EC2

Chapter 5 · Storage: S3

Chapter 6 · Networking: VPC

Chapter 7 · Identity and access: IAM

Chapter 8 · Managed databases

Chapter 9 · Why Infrastructure as Code

Chapter 10 · HCL: the Terraform language

Chapter 11 · Providers and state

Chapter 12 · Your first real infrastructure in Terraform

Chapter 13 · Load balancing and auto scaling

Chapter 14 · Serverless with Lambda

Chapter 15 · Messaging and events

Chapter 16 · Content delivery and DNS

Chapter 17 · Containers on AWS

Chapter 18 · Modules: reuse and composition

Chapter 19 · Workspaces and environment management

Chapter 20 · Remote backends and locking

Chapter 21 · Infrastructure testing

Chapter 22 · Terraform in CI/CD

Chapter 23 · Defense in depth

Chapter 24 · Observability: logs, metrics and traces

Chapter 25 · Cost optimization

Chapter 26 · High availability and disaster recovery

Chapter 27 · AWS Well-Architected Framework

Chapter 28 · Serverless architectures at scale

Chapter 29 · Data platforms on AWS

Chapter 30 · Multi-account and landing zones

Chapter 31 · Platform Engineering and Internal Developer Platform

Chapter 32 · Relevant AWS certifications

Chapter 33 · Projects to consolidate what you've learned

Chapter 34 · Resources and community

© Copyright 2024. All rights reserved