Templates, modules and infrastructure you can reuse
Why this matters
The first infrastructure-as-code file anybody writes is a single template that builds one environment with every value typed in. It works, and it does not survive contact with a second environment.
What happens next decides whether IaC helps or becomes a burden. The usual failure is to copy the template, change the values, and now maintain two near-identical files that slowly diverge — which reproduces the staging-is-not-like-production problem the code was supposed to solve, with extra steps.
This lesson is about the structures that prevent that: parameters, modules, outputs and a reviewed library. It is unglamorous and it is what separates an estate that can be rebuilt from one that merely has some templates in it.
The lesson
Parameters and variables that let one template build dev, test and production
The first refactor is to pull the values that differ between environments out of the template and into inputs.
What should be a parameter:
- Sizing — instance types, counts, storage sizes, scaling limits.
- Environment identity — names, tags, the environment label itself.
- Network addressing — the range each environment occupies.
- Toggles for optional pieces — whether a bastion exists, whether multi-zone resilience is enabled.
What should not be a parameter:
- Secrets. Never as a plain default, never in the repository, and ideally not passed as a variable at all — reference a secret store and let the platform resolve it at deploy time. Objective 4.5 covers this properly.
- Structural differences. If production needs a fundamentally different architecture, a flag that reshapes the template is a warning sign. The point of one template is that environments are the same shape at different sizes; once they are different shapes, staging stops predicting production.
Good practice on parameters themselves: give every one a type and a constraint so an invalid value fails at validation rather than halfway through an apply; give sensible defaults for the smallest environment, so the dangerous direction is explicit; and keep the values in per-environment files under source control rather than typed at the command line, so what produced an environment is recorded.
The result is one template, several small value files, and environments that differ only in the ways you intended.
Modules and nested templates, and the size at which splitting starts paying
A module is a reusable, parameterised group of resources with defined inputs and outputs — a function, in ordinary programming terms. Native tooling calls the equivalent nested or linked templates.
When to split, and the honest answer is later than enthusiasts suggest:
- A single environment of a few dozen resources is fine as one file. Splitting it adds indirection for no benefit.
- Split when a group of resources is used in more than one place — the standard network layout, the standard database with its subnet group and parameters, the standard web tier.
- Split when a file becomes hard to read, which in practice is a few hundred lines.
- Split along the rate of change: things that change together belong together. Networking changes rarely and applications change constantly, so separating them means a routine application change cannot accidentally alter the network.
The last point matters more than file size. Separating by blast radius — so an apply for one thing cannot damage another — is the structural decision that pays off, and it is why mature estates keep networking, shared data services and applications in separate stacks.
Two cautions:
- Nesting deeply is a trap. Modules calling modules calling modules makes it very hard to see what will be created. Two levels is usually enough.
- A module is an interface. Once shared, changing its inputs breaks its consumers, so it needs versioning like any other library.
Outputs and references, and how one stack consumes another's values
Once infrastructure is split, the pieces must refer to each other. An application stack needs the network's subnet identifiers; the database stack needs the security group.
Three approaches, in order of preference:
- Outputs and remote state references. The producing stack declares outputs; the consuming stack reads them. Explicit, versioned, and the dependency is visible in code.
- Data sources / lookups. The consumer queries the platform for a resource by tag or name. Loosely coupled and flexible, at the cost of a dependency that is not visible in either file — and a rename elsewhere breaks it silently.
- Hard-coded identifiers. Copying an identifier into a file. Always wrong. It breaks when the environment is rebuilt and makes the template environment-specific again.
The design rule is that dependencies point one way, from long-lived and slow-changing toward short-lived and fast-changing. Application stacks depend on network stacks; network stacks must never depend on application stacks. Circular dependencies between stacks cannot be resolved by the tool and force manual sequencing — and a scenario describing an environment that cannot be built from scratch in one pass is usually describing exactly that.
Keep outputs minimal and stable. Every output is a public interface, and removing one breaks whatever consumed it.
A registry of reviewed modules, and why copy-paste infrastructure diverges
Copy-paste is how most organisations start and it is why most organisations have forty subtly different network definitions.
The failure mode is specific: a template is copied, the copy is fixed for a local problem, and the fix never goes back. Six months later the original and the copy differ in ways nobody can account for, and a security improvement applied centrally reaches only some of them. This is the same divergence problem as the environments above, one level up.
A module registry — a versioned, reviewed library of shared modules — fixes it by making reuse easier than copying:
- Versioning. Consumers pin a version and upgrade deliberately. A change to a shared module does not break everything at once.
- Review and testing. Modules are reviewed and exercised before publication, so the shared pieces are the best-tested code in the estate.
- Policy baked in. Encryption on by default, logging enabled, sensible security group rules. A team using the standard module gets the compliant configuration without having to know the policy — which is far more effective than detecting violations afterwards.
- A curated, small set. A registry with two hundred modules nobody trusts is worse than none. Publish the handful that matter.
This is a strong answer to a familiar exam scenario: how do you ensure every team deploys resources meeting the security baseline? Guardrails that refuse non-compliant resources are one half; standard modules that make the compliant path the easy path are the other, and the combination is what actually works.
Testing a template: plan output, linting and a throwaway environment
Infrastructure code deserves testing, and there is a practical ladder of it, cheapest first.
- Format and lint. Style consistency and obvious errors. Instant, in the editor and in the pipeline.
- Validate. Syntax and internal consistency — do referenced values exist, are types right. No platform calls needed.
- Policy scan. Static analysis against rules: no public buckets, no unencrypted storage, no over-broad permissions, required tags present. Runs before anything is created, which is the whole value.
- Plan / what-if. Ask the tool what it would do against a real environment. This is the highest-value single check, and reading it properly means looking past the count of additions to:
- anything marked for replacement rather than in-place update, which usually means downtime and sometimes data loss;
- deletions that were not intended;
- changes to resources the author did not think they were touching.
- Deploy to a throwaway environment. The only way to know it actually works. Create it from nothing, run whatever assertions matter, destroy it. Doing this in the pipeline on every change is the strongest form of testing available, and the destroy step is also a test — infrastructure that cannot be cleanly destroyed is infrastructure you cannot cleanly rebuild.
A team that does the first four consistently and the fifth for anything shared has infrastructure code it can trust. A team that does none of them has automated the console clicks without gaining anything but speed.
What to take into the exam
- Parameterise sizing, names, addressing and toggles. Never secrets. If a flag changes the shape of an environment, staging stops predicting production.
- Split by blast radius and rate of change, not by line count. Dependencies point from slow-changing to fast-changing, never in a circle.
- Outputs and remote state are the right way to share values; hard-coded identifiers are always wrong.
- A versioned module registry with policy baked in makes the compliant path the easy path — the practical answer to "how do we ensure every team meets the baseline".
- The testing ladder is lint → validate → policy scan → plan → throwaway environment, and the plan line that says replace is the dangerous one.
Practise what you just read
1. Which value should be a template parameter?
Select one
Show answer
D. Sizing, names, addressing and optional toggles belong in parameters. Secrets never do, and the resource ordering is inferred by the tool from references rather than declared as an input.
2. A flag reshapes the environment so production has a different architecture from staging. Why is this a warning sign?
Select one
Show answer
A. One template with parameters works because environments are the same shape at different sizes. Once they are different shapes, testing in staging no longer tells you what production will do.
3. On what basis should infrastructure be split into separate stacks?
Select one
Show answer
B. Separating things that change at different rates means a routine application change cannot alter the network, because the two live in different stacks with different state. Bounding what a single apply can damage is the structural decision that actually pays off over time.
10 more questions on this objective are part of the full course.
Hands-on labs
Part of the free CompTIA Cloud+ CV0-004 course — 50 lessons and 86 hands-on labs.
This is an independent study companion for CompTIA Cloud+ CV0-004 and is not produced by or endorsed by CompTIA.