Docs & Code Guide

Keep infrastructure docs in sync with Terraform: generate, then regenerate

Terraform is the best case for documentation sync in all of engineering: the infrastructure is already text, reviewed and merged, which means most infrastructure docs can be generated from source instead of written about it. Module references regenerate from HCL with terraform-docs, the convention of a README per module keeps every doc next to what it describes, and the system-level diagram can regenerate from the same files on merge. What remains is knowing which of the three copies of reality you are trusting.

8 min readFor platform teams whose infra wiki describes the VPC of two years ago

See it as a diagram

Everything below, as a diagram you can edit. Describe yours and see it in seconds.

198/20003 credits left
Try:

No account needed · Editable canvas, not a picture

terraform-docs: the module reference writes itself

terraform-docs reads a module's HCL and emits its reference documentation: every input with type, default, and description, every output, providers, and requirements. Its inject mode rewrites only the region between two markers in an existing README, so hand-written context above the markers survives while the generated tables below them stay exact. The descriptions come from the description fields in the HCL itself, which puts the prose in the same file, and the same review, as the variable it describes.

Run it as a CI hook and the reference cannot drift: a merged variable change regenerates the table in the same push. The pre-commit variant catches it even earlier, at commit time on the author's machine.

# .github/workflows/tf-docs.yml
name: terraform-docs
on:
  push:
    branches: [main]
    paths: ["modules/**"]
jobs:
  docs:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with: { ref: main }
      - uses: terraform-docs/gh-actions@v1
        with:
          working-dir: modules/vpc,modules/ecs,modules/rds
          output-file: README.md
          output-method: inject   # rewrites only between the
          git-push: "true"        # BEGIN_TF_DOCS / END_TF_DOCS markers

The three-way drift problem

Infrastructure truth lives in three places that drift independently: the docs, the HCL config, and the state file recording what actually exists. Terraform itself only referees the second pair: terraform plan reveals config-versus-state drift, the console change someone made at 2am that the code does not know about. Docs-versus-config drift is what terraform-docs closes for module references. That leaves docs versus state, the subtlest gap: documentation generated faithfully from HCL still lies if the HCL itself has unapplied or drifted resources.

The practical discipline: generated docs describe the config, so keep the config true with drift detection, scheduled plan runs or a tool that alerts on out-of-band changes, and let the docs inherit that truth. Documenting the state directly is chasing a moving target; documenting a config you keep honest is a fixed one.

A README per module, docs next to their source

The convention the Terraform ecosystem settled on is one README.md at each module root, the file the Terraform Registry itself renders as the module's documentation page. It scales because it keeps every doc inside the blast radius of the change that could stale it: a PR touching modules/vpc shows modules/vpc/README.md sitting in the same diff, visibly regenerated or visibly forgotten. A central wiki page describing thirty modules has no such adjacency, which is why it is always wrong about at least five of them.

The generated tables cover the what; the hand-written paragraph above the markers covers the why, this module exists because, this input is dangerous when. That paragraph changes rarely, which is exactly the content hand-writing is good at, the same docs checks in CI principles applying to what remains.

The system view: regenerate the diagram on merge

terraform-docs documents one module at a time; nobody sees the system in an inputs table. The architecture diagram, the VPC, the services, the data stores and the boundaries between them, is the doc that answers "what are we running", and it can follow the same generate-then-regenerate discipline. Datadef's AI import accepts pasted Terraform as the prompt, so the first version of the diagram comes from the HCL itself; the Terraform infrastructure diagram generator is the entry point, and living diagram from Terraform covers the source-specific details.

For the ongoing loop, an AI agent connected to the Datadef MCP server (registry name io.datadef/mcp) reads the changed HCL and updates the diagram, invoked as a CI step on merges touching the infrastructure paths or on request during review; it needs an API key, available on paid plans. To be exact: Datadef does not watch the repository and nothing fires without the wired trigger. Shared public, the diagram embeds by URL in the wiki and the runbooks, and the updated version appears everywhere within minutes of the agent's edit.

The three-way drift caveat

Generation covers references and diagrams, not judgment: why the VPC is carved this way, what the IAM boundary is defending against, which module is deprecated for new services. That prose stays human, and the module-README convention at least keeps it adjacent to the code it explains. The live diagram embed requires a public Datadef project, a real constraint for infrastructure whose shape is confidential; the private-repo fallback is agent-regenerated exports committed alongside the HCL. And the regeneration loop is only as reliable as the CI trigger you wire: a paths filter that misses a module directory is a diagram that silently stops following it.

The layering

terraform-docs keeps every module reference exact, drift detection keeps the config honest against the state, and a merge-triggered agent keeps the system diagram matching the HCL. Humans write only the why.

FAQ

How do I keep infrastructure documentation up to date?

Generate everything generatable from the Terraform itself. terraform-docs regenerates each module's reference tables from HCL in CI on every merge, the README-per-module convention keeps docs in the same diff as the change that could stale them, and the system-level architecture diagram regenerates from the same files via an MCP-connected AI agent triggered on merge. Hand-write only the why: purposes, boundaries, deprecations.

What does terraform-docs do?

It reads a Terraform module's HCL and generates reference documentation: inputs with types, defaults, and descriptions, outputs, providers, and requirements. In inject mode it rewrites only the region between BEGIN_TF_DOCS and END_TF_DOCS markers in the README, preserving hand-written content around it. Run via its GitHub Action or pre-commit hook, it keeps module references exact on every merge.

What is the three-way drift problem in infrastructure docs?

Docs, Terraform config, and Terraform state are three copies of reality that drift independently. terraform plan detects config-versus-state drift, such as manual console changes. terraform-docs closes docs-versus-config drift for module references. The remaining gap is docs versus reality when the config itself has drifted, which is why scheduled plan runs or drift-detection tooling underpin everything generated from the HCL.

Can I generate an architecture diagram from Terraform?

Yes. Datadef's AI import accepts pasted Terraform as the prompt and generates an editable architecture diagram showing the network zones, services, and data stores the HCL defines. From there an AI agent connected to the Datadef MCP server can update the diagram when infrastructure merges change the HCL, invoked from CI. Shared public, the diagram embeds by URL in wikis and runbooks and follows edits within minutes.

Does the infrastructure diagram update when Terraform changes?

When a wired trigger fires, yes; by itself, no. Datadef does not watch repositories. The working pattern is a CI workflow filtered on infrastructure paths that invokes an agent connected to the Datadef MCP server to update the diagram from the merged HCL. Once updated, every embed of the diagram shows the new version within minutes because the embed URLs serve the current render.