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
})
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
})
This matters because:
-
path.modulealways 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
ingressandegressblocks - separate
aws_security_group_ruleresources
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"
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"
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"
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:
-
devusedv0.0.2 -
productionstayed onv0.0.1
That is the real pattern teams use:
-
devtests the newer module version -
productionstays 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:
-
devpicked up the new module version and exposed the additional output I added -
productionremained 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)