Undocumented modules become tribal knowledge
A module with no documentation has exactly one real user: the person who wrote it. Everyone else reads the source, guesses what each variable does, and copies an old call from another repository. When that author leaves, the module becomes something nobody wants to touch.
Terraform module documentation is therefore less about tidiness and more about whether the investment in shared modules pays back. It is the final piece of the reusable modules we build in our Terraform and infrastructure as code service, alongside versioning and review.
What good looks like
An engineer who has never seen the module should be able to answer four questions in five minutes:
- What does this module create, and what does it leave out?
- What is the smallest call that works?
- Which inputs matter, and what do the defaults do?
- What do I get back, and how do I use it elsewhere?
If the README cannot answer those, the module is not finished.
How it works in practice
Descriptions are the documentation
Most of the reference comes straight from variable and output blocks. That makes careful descriptions the highest-leverage documentation work:
variable "backup_retention_days" {
type = number
description = "Days to keep automated database backups. Set to 0 to disable. Production should use 7 or more."
default = 7
validation {
condition = var.backup_retention_days >= 0 && var.backup_retention_days <= 35
error_message = "Backup retention must be between 0 and 35 days."
}
}
output "endpoint" {
description = "Connection hostname of the database instance. Pair it with the port output."
value = aws_db_instance.this.address
}
A good description states the unit, the effect and the safe range. A bad one repeats the name: “The backup retention days”.
Generating the reference
terraform-docs reads the module and produces Markdown. A small configuration file keeps output identical across modules:
# .terraform-docs.yml
formatter: markdown table
output:
file: README.md
mode: inject
sort:
by: required
In inject mode it rewrites only the section between its marker comments, so hand-written text around it is untouched. A pre-commit hook runs it locally, and CI repeats the run and fails if the README differs, so out-of-date docs cannot merge.
The human sections
Generated tables do not explain why. Each README opens with a short written section covering:
- Purpose: one paragraph on what problem the module solves.
- Design decisions: why it is opinionated in the ways it is, for example “always encrypts, never public”.
- Not included: what you must provide separately, such as DNS or alarms.
- Upgrade notes: links to the changelog, tied to module versions.
Examples that cannot rot
An examples/ folder with a minimal and a complete call is more useful than paragraphs of prose. We validate them automatically:
for dir in examples/*/; do
terraform -chdir="$dir" init -backend=false -input=false
terraform -chdir="$dir" validate
done
For deeper checks, the native terraform test command can run plan-level assertions against the example in a sandbox. Broken examples then fail the build instead of misleading a reader six months later.
A README skeleton worth reusing
Consistency matters more than cleverness, because engineers learn where to look. Every module README follows the same order:
# terraform-aws-example
One paragraph: what this module creates and the problem it solves.
## Usage
(minimal call, copied from examples/minimal)
## Design decisions
(why it is opinionated; what it will never do)
## Not included
(what the caller must provide)
<!-- BEGIN_TF_DOCS -->
<!-- END_TF_DOCS -->
## Upgrading
(link to CHANGELOG.md and any migration notes)
The markers are where terraform-docs injects the generated tables. Everything outside them is written by people, and everything inside is owned by the tool.
Documenting the sharp edges
The most valuable sentences in a README are the warnings. Changing a particular input may force replacement of a database. A module may need a provider alias passed in. An output may be marked sensitive. We ask module authors to list these under a short heading so a reviewer, or a new engineer on call, finds them without reading the source.
Decisions and trade-offs
README only or a docs site. A README per module is the floor. A site built from those READMEs adds search and navigation but needs hosting and upkeep. We only add it when the module count justifies it.
How much prose. Short beats exhaustive. A page that takes ten minutes to read will not be read. Link to cloud provider documentation for provider behaviour instead of restating it.
Documenting wrapped community modules. When your module wraps an upstream one, document your additions and policy, and link upstream for the rest. Copying their reference creates a maintenance burden.
When not to polish. A module used in one place for a short-lived purpose needs a purpose line and a usage snippet, nothing more.
Failure modes we avoid
- Hand-maintained tables that stop matching reality within weeks.
- Examples with real identifiers such as account IDs or secrets. We use obviously fake values.
- Missing defaults documentation, which leads people to set values they did not need to set.
- Silent behaviour. If a module creates something that costs money, such as a NAT gateway, the README says so.
Keeping documentation honest over time
Generated reference cannot go stale, but the human sections can. Two habits help. First, the pull request template for module changes includes a single question: does this change alter behaviour that the README describes? Second, the changelog is updated in the same pull request as the code, so the upgrade notes and the version tag tell the same story. Reviewers then have something concrete to check, instead of a vague instruction to keep docs tidy.
Delivery and handover
You receive documented modules, the generation setup, a README template for new modules, and a checklist for reviewing module pull requests. Documentation quality becomes part of how your team reviews code, which we reinforce in training and code review.
How to tell it is working
New engineers use a module without asking the author a question. Pull requests that change inputs also change the README automatically. Examples build green. To get your modules into this state, book a consultation.