Ravindra BagaleCourses & study guides मराठी

Chapter 35: Terraform Modules Writing Using Registry and Versioning

35.4 Version your own modules

Story. An Infosys client platform team changed a shared module and broke three product teams on the same morning, because everyone pointed at the latest code. Version tags fix that. Publish the module from a Git repository with tags such as v1.2.0, and each caller pins a ref. Upgrades become a reviewed one-line change in each caller.

module repo tag v1.2.0 caller ?ref=v1.2.0 init -upgrade plan review
Callers pin a tag; upgrading is a reviewed change from v1.2.0 to v1.3.0.
# in the module repository
git tag -a v1.2.0 -m "web-server: add IMDSv2 requirement"
git push origin v1.2.0

# in the caller (placeholder host - use your company Git server)
module "web" {
  source = "git::https://git.example.com/platform/tf-web-server.git?ref=v1.2.0"
  name   = "badge-dev-web"
}

terraform init -upgrade
terraform plan

What each line does

  • git tag -a v1.2.0 -m "..." — Creates an annotated release tag on the module code.
  • git push origin v1.2.0 — Publishes the tag so callers can use it.
  • source = "git::https://git.example.com/...?ref=v1.2.0" — Downloads the module from Git at exactly that tag; replace the placeholder host.
  • terraform init -upgrade — Fetches the new module version after you change the ref.
  • terraform plan — Shows exactly what the new module version would change.

When it is green.

Downloading git::https://git.example.com/platform/tf-web-server.git?ref=v1.2.0 for web...
  ~ resource "aws_instance" "this" {
      ~ metadata_options { ~ http_tokens = "optional" -> "required" }
    }
Plan: 0 to add, 1 to change, 0 to destroy.

When it is red.

Error: Failed to download module
  Could not download module "web" source code from "git::https://...?ref=v1.3.0":
  error downloading: exit status 128 - fatal: Remote branch v1.3.0 not found

Fix. The tag does not exist yet or was not pushed. Run git push origin with the tag name, or fix the ref.

What: every term in plain English

  • semantic version — A tag like v1.4.2: major.minor.patch.
  • major — Breaking change. Callers must change code, such as a renamed input.
  • minor — New feature that does not break callers, such as a new optional input.
  • patch — A fix with no new inputs.
  • ref — The Git tag or branch in a git:: source, such as ?ref=v1.2.0.
  • CHANGELOG.md — A file listing what changed in each version.
  • private registry — A company registry, such as Terraform Cloud or GitLab, that hosts modules by version.

Example 1: Release v1.0.0 of a module

An HCL Tech platform team kept the web-server module in its own Git repo, so each release gets a tag.

  1. Create a repo called tf-web-server with the module files at the root.
  2. Commit and tag v1.0.0.
  3. Push the tag.
cd tf-web-server
git init -b main
git add .
git commit -m "web-server module first release"
git tag -a v1.0.0 -m "v1.0.0: server, security group, outputs"
git remote add origin https://git.example.com/platform/tf-web-server.git
git push origin main --tags
git tag -l

Output

[main (root-commit) 3f2a1b0] web-server module first release
 4 files changed, 92 insertions(+)
 * [new branch]      main -> main
 * [new tag]         v1.0.0 -> v1.0.0
v1.0.0

What it shows: git.example.com is a placeholder. Use your company Git server.

Example 2: Call the module at a tag

Product teams pin the tag. The module can change on main without touching them.

module "web" {
  source    = "git::https://git.example.com/platform/tf-web-server.git?ref=v1.0.0"
  name      = "orders-dev-web"
  vpc_id    = data.aws_vpc.default.id
  subnet_id = data.aws_subnets.default.ids[0]
}
terraform init
terraform plan

Output

Initializing modules...
Downloading git::https://git.example.com/platform/tf-web-server.git?ref=v1.0.0 for web...
- web in .terraform/modules/web

Plan: 2 to add, 0 to change, 0 to destroy.

What it shows: For SSH access use git::ssh://git@git.example.com/platform/tf-web-server.git?ref=v1.0.0.

Example 3: Ship a minor release and upgrade one caller

The platform team added IMDSv2 as a safe default. That is v1.1.0. The orders team upgrades first in dev.

# in the module: main.tf
resource "aws_instance" "this" {
  # ...
  metadata_options {
    http_tokens = "required"
  }
}
# module repo
git commit -am "require IMDSv2"
git tag -a v1.1.0 -m "v1.1.0: IMDSv2 required"
git push origin v1.1.0

# caller repo: change ?ref=v1.0.0 to ?ref=v1.1.0, then
terraform init -upgrade
terraform plan

Output

Downloading git::https://git.example.com/platform/tf-web-server.git?ref=v1.1.0 for web...

  # module.web.aws_instance.this will be updated in-place
  ~ resource "aws_instance" "this" {
      ~ metadata_options {
          ~ http_tokens = "optional" -> "required"
        }
    }

Plan: 0 to add, 1 to change, 0 to destroy.

What it shows: In-place update, no server rebuild. Prod upgrades only after dev is green.

Example 4: Major release: renamed input with a moved block

The team renamed resource this to server in v2.0.0. Without a moved block, every caller would see a destroy.

# in the module v2.0.0: main.tf
resource "aws_instance" "server" {
  # ...
}

moved {
  from = aws_instance.this
  to   = aws_instance.server
}
# CHANGELOG.md
## v2.0.0
- BREAKING: input size renamed to instance_type
- resource renamed this -> server (moved block included, no destroy)
## v1.1.0
- IMDSv2 required
## v1.0.0
- first release
terraform init -upgrade
terraform plan

Output

  # module.web.aws_instance.this has moved to module.web.aws_instance.server
    resource "aws_instance" "server" {
        id = "i-0123456789abcdef0"
    }

Plan: 0 to add, 0 to change, 0 to destroy.

What it shows: Major means callers read the CHANGELOG. The moved block keeps the real server safe.

Command cheat sheet

Command What it does When to use it
git tag -​a v1.​2.​0 -​m "..." Make a release Each module change
git push origin v1.​2.​0 Publish the release Right after tagging
?ref=v1.2.0 Pin a Git module Every git:: source
terraform init -​upgrade Download the new ref After changing ref
git tag -l List releases Before choosing a version
moved { from to } Keep resources on rename Major releases

Verify: did it work?

  1. Tag exists

    git ls-remote --tags origin
    

    Output

    3f2a1b0...  refs/tags/v1.1.0
    
  2. Caller uses the tag

    grep ref= main.tf
    

    Output

    source = "git::https://git.example.com/platform/tf-web-server.git?ref=v1.1.0"
    
  3. Upgrade is safe

    terraform plan
    

    Output

    Plan: 0 to add, 1 to change, 0 to destroy.
    

When it is red 1

Error: Failed to download module
  error downloading: exit status 128 - fatal: Remote branch v1.3.0 not found

Cause. The tag was not pushed, or has a typo.

Fix, step by step

  1. Run git ls-remote --tags origin.
  2. Push the tag or fix the ref.

When it is red 2

Error: Failed to download module
  fatal: could not read Username for 'https://git.example.com': terminal prompts disabled

Cause. The CI runner has no Git access.

Fix, step by step

  1. Use an SSH deploy key or a CI token stored as a secret.
  2. Never write the token in the .tf file.

When it is red 3

  # module.web.aws_instance.this will be destroyed
  # module.web.aws_instance.server will be created
Plan: 1 to add, 0 to change, 1 to destroy.

Cause. The module renamed a resource with no moved block.

Fix, step by step

  1. Do not apply.
  2. Add a moved block in the module and release a patch.

Cleanup

terraform destroy -auto-approve

Ravindra Bagale's Tip

Never point callers at main. A tag is a promise; a branch is a moving target.

Practice tasks

Easy Tag a release

Why: Start versioning.

What to do: Tag your module v1.0.0 and push it.

How:
  1. git tag -a, git push --tags.

Check: git tag -l shows it.

Medium Pin a caller

Why: Stable callers.

What to do: Change a caller to ?ref=v1.0.0.

How:
  1. init, plan.

Check: No changes.

Medium Minor upgrade

Why: Safe change.

What to do: Add an optional input, tag v1.1.0, upgrade dev.

How:
  1. init -upgrade, plan.

Check: 0 to destroy.

Hard Major with moved

Why: No surprise destroy.

What to do: Rename a resource, add moved, tag v2.0.0.

How:
  1. Write CHANGELOG.
  2. Upgrade a caller.

Check: Plan shows has moved and 0 to destroy.

Easy Say it in two lines

Why: If you can say it simply, you understand it.

What to do: Write two short lines about this lesson for a fresher.

How:
  1. Write the problem it solves.
  2. Write the one command or file you use.
  3. Read it aloud.

Check: A fresher understands it without a question.

Easy Start a safe lab folder

Why: Practice needs a throwaway place.

What to do: Make a new folder with versions.tf and a random_pet resource, so no AWS cost.

How:
  1. mkdir tf-lab and cd into it.
  2. Add the hashicorp/random provider and one random_pet.
  3. Run terraform init and terraform apply.

Check: Apply complete! with 1 added.

Medium Break it on purpose

Why: You must know the red output by face.

What to do: Cause one error from the red part of this lesson and fix it.

How:
  1. Follow one red section.
  2. Copy the exact message to your notes.
  3. Write the fix in one line.

Check: You fixed it without searching the web.

Medium Run it twice

Why: A second run must be quiet.

What to do: Run the same plan two times with no edits.

How:
  1. Run terraform plan.
  2. Run it again.
  3. Explain any change it still shows.

Check: The second plan says No changes, or you can explain each line.

Medium Write a runbook page

Why: At 2 a.m. nobody remembers flags.

What to do: Write a one-page runbook for this lesson.

How:
  1. Title, when to use, exact commands, how to check, how to undo.

Check: A teammate can follow it alone.

Medium Run it from a pipeline

Why: Real teams run Terraform from CI, not from laptops.

What to do: Run the main command of this lesson inside a GitHub Actions or Jenkins job.

How:
  1. Add a job with hashicorp/setup-terraform or a Terraform Docker image.
  2. Give it AWS access through an IAM role, never a key in the file.
  3. Run init, then the command from this lesson.

Check: The job log shows the same output you saw on your laptop.

Medium Check the cost

Why: Labs left running cost real money.

What to do: Find what this lesson would cost per day if you forgot to destroy it.

How:
  1. Open the AWS Pricing Calculator.
  2. Add each resource from your plan.
  3. Write the daily cost in your notes.

Check: You know the daily cost and you ran terraform destroy.

Hard Draw the flow top to bottom

Why: A drawing shows the order of work.

What to do: Draw this lesson as boxes from top to bottom.

How:
  1. Start at your code, go through plan and state, end at AWS.
  2. Mark where each command reads or writes.

Check: Each box matches a real step you ran.

Hard Explain it like an interview

Why: Interviewers want real stories.

What to do: Record a 60 second answer about this lesson.

How:
  1. Say the problem, the command, and one error you hit.

Check: Your answer has a real command and a real error message.

Homework: try at home

Try at home

Write a CHANGELOG.md for a module with three releases. For each, say if it is major, minor or patch and why.

Recap

  • Tag every module release with semantic versions.
  • Callers pin ?ref=TAG, never a branch.
  • Upgrades go dev first, through a reviewed plan.
  • Major releases need a CHANGELOG and moved blocks.
  • Keep Git tokens out of code.

FAQ

Can I use a branch as ref?

It works, but the code changes under you. Use tags.

Can I delete a tag?

Avoid it. Callers may use it. Release a new patch instead.

Git or private registry?

Git tags are simple. A private registry adds a version picker and search.

How do I use version = with Git?

You cannot. version works only for registry sources. Use ?ref= for Git.

Monorepo or one repo per module?

Both work. One repo per module makes tags simple.

Interview questions

  1. How do you version Terraform modules?

    Keep them in Git, tag releases with semantic versions, and callers pin ?ref=TAG.

  2. What is a breaking change in a module?

    Removing or renaming an input or output, or a change that forces replacement.

  3. How do you rename a resource inside a shared module?

    Add a moved block in the module so callers see no destroy.

  4. How do callers upgrade?

    Change ref in a branch, init -upgrade, plan in dev, then promote.

  5. How does CI download a private Git module?

    With an SSH deploy key or a token from the CI secret store, never in code.

Quick quiz

Q1. What does the 2 in v2.0.0 mean?

Major, a breaking change.

Q2. How do you pin a Git module?

?ref=v1.2.0

Q3. Which command fetches the new ref?

terraform init -upgrade

Q4. Can you use version = with a Git source?

No.

Q5. What keeps a renamed resource safe?

A moved block.

Lab

  1. Create modules/web-server from 35.2.
  2. Create envs/dev/main.tf calling the registry VPC module and your module.
  3. Run terraform init and terraform plan; count the resources to add.
  4. Run terraform apply and print web_ip.
  5. Change the module default instance type and run plan to see the update.
  6. Run terraform destroy to avoid cost.

Practice task

List the five inputs and two outputs you would give a reusable S3 bucket module, and explain why.

Ravindra Bagale's Tip

A module with forty inputs is just a copy with extra steps. Expose only what callers should change.

In short

  • A module is a folder with inputs, resources and outputs.
  • The root module calls child modules; outputs wire into inputs.
  • Registry modules are pinned with a version range.
  • Your own modules are pinned with Git tags and upgraded through review.

Samajla ka?

Ravi Sir's Cards for this lesson

मराठी cards