Skip to main content
Cloud DevOps Solutions
Get a quote Talk to an engineer

Terraform / IaC

Documenting Terraform Modules: Inputs, Outputs and Working Examples

We document every Terraform module with generated tables of inputs, outputs and requirements, plus a short purpose statement and runnable examples. Generation runs in CI, so the reference never drifts from the code, and humans write only the parts that explain intent.

Key takeaways

  • Hand-written parameter tables go stale; generate them from the code with terraform-docs.
  • Descriptions on variables and outputs are the real documentation, because they feed the generated reference.
  • Every module needs at least one working example that is itself tested.
  • Good module docs say when not to use the module and what it deliberately does not do.
  • A CI check that fails on out-of-date docs is what keeps the habit.

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:

  1. What does this module create, and what does it leave out?
  2. What is the smallest call that works?
  3. Which inputs matter, and what do the defaults do?
  4. 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.

References

How we deliver it

  1. 01

    Audit current docs

    We review which modules have READMEs, whether variables have descriptions, and how accurate the examples are.

  2. 02

    Fix the source of truth

    Missing descriptions, types and validation messages are added to variables and outputs, because generation depends on them.

  3. 03

    Add generation

    terraform-docs is configured per module and run through pre-commit and CI so output is consistent.

  4. 04

    Write the human parts

    Each README gets a purpose statement, design notes, limits and a usage guide in plain language.

  5. 05

    Build tested examples

    An examples directory holds minimal and realistic calls, validated in CI so they cannot rot.

Tools and technologies

terraform-docsTerraformOpenTofupre-committflintterraform validateterraform testGitHub ActionsMarkdownMkDocs

Frequently asked questions

What should Terraform module documentation include?
A one-paragraph purpose, a minimal usage example, a table of inputs with types, defaults and descriptions, a table of outputs, provider and Terraform version requirements, and notes on limits and design choices. Generated reference covers the tables; people write the rest.
What is terraform-docs?
terraform-docs is an open source tool that reads your Terraform files and generates documentation, usually Markdown tables of variables, outputs, providers and requirements. It can inject the result into a README between marker comments, which makes regenerating on every change simple and safe.
How do we keep module docs from going stale?
Generate them and enforce it. Run terraform-docs in a pre-commit hook and in CI with a check that fails if the committed README differs from freshly generated output. Docs that cannot drift do not need anyone to remember to update them.
Should every module have an examples folder?
Yes, ideally with a minimal example and one realistic one. Examples show how inputs combine, double as test fixtures, and are the first place engineers look. We validate them in CI, so a variable rename that breaks an example is caught immediately.
Where should we publish module docs?
A README in each module repository is the baseline and renders in any Git host. A private registry also shows the README and inputs. For a platform with many modules, a small documentation site built from those READMEs gives a searchable catalogue.

Talk to an engineer about terraform module documentation

Book a free 30-minute call. We review your current setup and tell you honestly whether and how we can help.

Written by Saddique Khan, DevOps / SRE / Cloud Consultant Engineer. Last updated 2026-10-08.