DEV Community

Mary Mutua
Mary Mutua

Posted on

Advanced Terraform Module Usage: Versioning, Gotchas, and Reuse Across Environments

Day 9 of my Terraform journey focused on the part of modules that feels more real-world: not just creating them, but using them safely across environments.

After building my first reusable module on Day 8, today I went deeper into:

  • module gotchas
  • version pinning
  • using different module versions in different environments

This is where Terraform modules start to feel production-ready.

Why This Matters

A module is useful when it is reusable.

A module becomes powerful when:

  • it is versioned
  • environments can adopt changes at different times
  • teams can avoid accidental breakage from untested module updates

That was the core lesson of Day 9.

Gotcha 1: File Paths Inside Modules

One easy mistake is referencing files inside a module using a plain relative path like this:

user_data = templatefile("./user-data.sh", {
  server_port = var.server_port
})
Enter fullscreen mode Exit fullscreen mode

The problem is that Terraform may resolve that path relative to where Terraform is run, not relative to the module itself.

The safer pattern is:

user_data = templatefile("${path.module}/user-data.sh", {
  server_port = var.server_port
})
Enter fullscreen mode Exit fullscreen mode

This matters because:

  • path.module always points to the module’s own folder
  • file lookups stay predictable
  • the module works correctly when called from different root configurations

Gotcha 2: Inline Blocks vs Separate Resources

Some Terraform resources can be configured in two ways.

For example, security groups can use:

  • inline ingress and egress blocks
  • separate aws_security_group_rule resources

Mixing both patterns can cause conflicts and make the module harder to extend.

For reusable modules, separate resources are usually the better choice because they are more flexible and easier for callers to extend without editing the module itself.

Gotcha 3: Broad Module Dependencies

Another subtle issue is depending on an entire module with depends_on.

If a root configuration depends on a whole module, Terraform can treat the entire module as the dependency instead of just the specific resource or output that was actually needed.

That can create unnecessary dependency chains and make plans more confusing than they need to be.

The better pattern is:

  • expose more specific outputs
  • depend on the exact value you need
  • avoid broad module-level dependencies unless they are truly necessary

Versioning the Module

This was the most practical part of the day.

Instead of calling the module only from a local path, I versioned it with Git tags and pinned environments to specific versions.

Local Source

For quick development, a local source is useful:

source = "../../../../modules/services/webserver-cluster"
Enter fullscreen mode Exit fullscreen mode

This is great for fast iteration, but not ideal for shared environments.

Git Source

For versioned usage, I used a Git source with ref=:

source = "github.com/mary20205090/30-day-Terraform-Challenge//day_8/modules/services/webserver-cluster?ref=v0.0.1"
Enter fullscreen mode Exit fullscreen mode

This means:

  • pull the module from GitHub
  • use the module subdirectory
  • pin it to the exact version tag v0.0.1

Registry Source

A registry source is cleaner when modules are published more formally:

source  = "namespace/webserver-cluster/aws"
version = "1.0.0"
Enter fullscreen mode Exit fullscreen mode

That is easier to read, but the Git source pattern was enough for my lab.

Multi-Environment Versioning Pattern

I then used different module versions intentionally across environments:

  • dev used v0.0.2
  • production stayed on v0.0.1

That is the real pattern teams use:

  • dev tests the newer module version
  • production stays pinned to the older stable version until the new one is validated

This makes module rollouts safer and reduces the risk of untested changes reaching production too early.

What I Observed

In my Day 9 setup:

  • dev picked up the new module version and exposed the additional output I added
  • production remained on the previous version and continued using the older stable module behavior

That confirmed the main idea:

  • same reusable module
  • different pinned versions
  • different environments can move at different speeds safely

Takeaway

Day 9 made one thing very clear to me: creating a reusable module is only the beginning.

The real strength comes from:

  • understanding the gotchas
  • versioning modules properly
  • testing newer versions in lower-risk environments
  • promoting those versions only when they are ready

That is what makes Terraform modules practical for teams at scale.

Full Code

πŸ‘‰ Github LInk

Follow My Journey

This is Day 9 of my 30-Day Terraform Challenge.

See you on Day 10 πŸš€

Top comments (0)