Engineering Authoritative Documentation for Terraform Modules with Terraform-docs

Terraform has fundamentally shifted the paradigm of infrastructure management by treating infrastructure as code, allowing engineers to version, share, and reuse datacenter blueprints with the same rigor applied to application software. However, the operational value of a Terraform module is not derived solely from its functional correctness within .tf files; it is equally dependent on the clarity, accuracy, and accessibility of its documentation. As modules grow in complexity, integrating cloud providers, custom resources, and nested dependencies, manual documentation becomes a bottleneck prone to drift. This is where terraform-docs emerges as a critical component of the developer workflow. It is a command-line tool designed to generate documentation for Terraform modules and providers from their source code. By parsing the module structure, terraform-docs automatically extracts inputs, outputs, requirements, and resources, rendering them into human-readable formats such as Markdown or AsciiDoc. This eliminates the risk of documentation divergence, ensuring that the README.md file always reflects the current state of the code.

Understanding the Terraform Ecosystem and Documentation Challenges

To appreciate the utility of terraform-docs, one must first understand the architectural principles of Terraform itself. Terraform is a tool for building, changing, and versioning infrastructure safely and efficiently. It manages both existing popular service providers and custom in-house solutions. The platform rests on four key pillars that directly impact how documentation is generated.

First, Infrastructure as Code requires that infrastructure be described using a high-level configuration syntax. This allows a blueprint of the datacenter to be versioned and treated as standard code. Second, the Execution Plans feature introduces a planning step where Terraform generates an execution plan, showing exactly what changes will occur before the apply command is run. Third, the Resource Graph allows Terraform to build a graph of all resources and parallelize the creation or modification of non-dependent resources, optimizing efficiency. Finally, Change Automation enables complex changesets to be applied with minimal human interaction.

Because Terraform relies on a declarative model, the .tf files contain rich metadata about inputs, outputs, and provider requirements. However, this metadata is machine-readable, not human-readable. Without a translation layer, consumers of a module must manually parse HCL code to understand required variables, default values, and constraints. terraform-docs automates this translation. It parses the module root, identifies the structural components, and formats them into a consistent template. This is particularly vital for open-source modules published on registries, where clear documentation is the primary factor in adoption.

Installation and Platform Support

terraform-docs is available across major operating systems, including Linux, macOS, Windows, and FreeBSD. The installation process varies slightly depending on the platform and the preferred package management strategy. The tool version referenced in current documentation workflows is v0.24.0, which is a stable release.

For users on macOS, Homebrew provides a streamlined installation path. The command brew install terraform-docs or the more specific brew install terraform-docs/tap/terraform-docs installs the latest stable binary. Windows users have two primary package manager options: Scoop and Chocolatey. To use Scoop, users must first add the dedicated bucket and then install the package using scoop bucket add terraform-docs https://github.com/terraform-docs/scoop-bucket followed by scoop install terraform-docs. For Chocolatey, the installation is direct via choco install terraform-docs.

For Go developers, installation is handled through the Go module system. For Go 1.17 and later, the command go install github.com/terraform-docs/[email protected] places the binary in the user's GOPATH bin directory. For Go 1.16, the environment variable GO111MODULE="on" is required, and the command becomes GO111MODULE="on" go get github.com/terraform-docs/[email protected]. It is important to note a historical naming change; for versions v0.9.1 and earlier, the old module namespace under segmentio was used, requiring GO111MODULE="on" go get github.com/segmentio/[email protected].

Pre-compiled binaries are available on the GitHub Release page for all supported platforms. Users can download the appropriate asset using curl and tar. The following sequence downloads the binary for the current architecture, extracts it, sets executable permissions, and moves it to a directory in the system PATH:

bash curl -sSLo ./terraform-docs.tar.gz https://terraform-docs.io/dl/v0.24.0/terraform-docs-v0.24.0-$(uname)-amd64.tar.gz tar -xzf terraform-docs.tar.gz chmod +x terraform-docs mv terraform-docs /some-dir-in-your-PATH/terraform-docs

A common post-installation issue is the terraform-docs: command not found error. This occurs if the directory containing the binary is not included in the system's $PATH. In such cases, users can either add the directory to the $PATH or perform a manual build by cloning the repository and running make build. This manual build places the binary in a specific location determined by the Go environment and system architecture:

bash $(go env GOPATH)/src/github.com/terraform-docs/terraform-docs/bin/$(uname | tr '[:upper:]' '[:lower:]')-amd64/terraform-docs

Additionally, code completion for bash and zsh shells can be installed to enhance the developer experience.

Docker and Containerized Execution

For environments where installing system binaries is restricted, or for CI/CD pipelines requiring isolated tooling, terraform-docs can be run as a Docker container. The container image is hosted on Quay.io. To run the tool, the current directory containing the Terraform .tf files is mounted into the container's /terraform-docs volume. The command below demonstrates how to execute the tool within the container:

bash docker run --rm --volume "$(pwd):/terraform-docs" -u $(id -u) quay.io/terraform-docs/terraform-docs:0.24.0 markdown /terraform-docs

The tag latest in the Docker image refers to the latest stable released version, while the edge tag refers to the HEAD of the master branch at any given point in time. Named version tags are identical to the official GitHub releases without the leading v.

If the Terraform module configuration does not have output.file enabled, the generated documentation can be redirected to a file using standard shell redirection. For example, generating Markdown output into a file named doc.md is achieved as follows:

bash docker run --rm --volume "$(pwd):/terraform-docs" -u $(id -u) quay.io/terraform-docs/terraform-docs:0.24.0 markdown /terraform-docs > doc.md

Basic Usage and Command Execution

The primary use case for terraform-docs is to generate documentation within a directory. The most common command generates a Markdown table and injects it into the README.md file. The command syntax is:

bash terraform-docs markdown table --output-file README.md --output-mode inject /path/to/module

The --output-mode inject flag ensures that the generated content is placed between specific markers in the README file, allowing manual content to be preserved outside the generated block. The tool supports various output formats, including Markdown and AsciiDoc. The generated content can be customized further via configuration files, but the default behavior provides a robust table of inputs, outputs, and requirements.

Configuration and Customization

terraform-docs is highly configurable via a YAML file. The default name of this file is .terraform-docs.yml. The tool searches for this file in a specific path order:
1. Root of the module directory
2. .config/ folder at the root of the module directory
3. Current directory
4. .config/ folder at the current directory
5. $HOME/.tfdocs.d/

The configuration file allows deep customization of the generated output. Below is a table detailing the key configuration options and their default values:

Configuration Key Description Default Value
formatter The output format (e.g., markdown, asciidoc). Required. ""
version Version string for the tool output. ""
header-from File to read the header from. main.tf
footer-from File to read the footer from. ""
recursive.enabled Whether to recursively check submodules. false
recursive.path Path to recursive modules. modules
include-main Whether to include the main module in the list. true
sections.hide List of sections to hide. []
sections.show List of sections to show. []
output.file Target file for output. ""
output.mode Injection mode (inject, overwrite, replace). inject
output-values.enabled Whether to include output values. false
sort.enabled Whether to sort inputs/outputs. true
sort.by Field to sort by (name, description). name
settings.anchor Add HTML anchors to headings. true
settings.color Use colored output. true
settings.default Show default values. true
settings.description Show descriptions. false
settings.escape Escape HTML characters. true
settings.hide-empty Hide empty sections. false
settings.html Output HTML. true
settings.indent Indentation level for nested structures. 2
settings.lockfile Show lockfile info. true
settings.read-comments Read comments from files. true
settings.required Show required status. true
settings.sensitive Show sensitive status. true
settings.type Show type information. true

The output block controls how the generated content is written. The template field allows for custom Go templates. The default template wraps the content in HTML comments to facilitate injection:

yaml template: |- <!-- BEGIN_TF_DOCS --> {{ .Content }} <!-- END_TF_DOCS -->

Advanced Templating and Content Generation

The content field in the configuration allows for advanced customization using Go templates. If the content field is empty, the default order of sections is used. However, when content is defined, it takes precedence over sections.show and sections.hide. The content is a Go template that supports various variables representing the generated sections.

The following variables are available in the template:
- {{ .Header }}
- {{ .Footer }}
- {{ .Inputs }}
- {{ .Modules }}
- {{ .Outputs }}
- {{ .Providers }}
- {{ .Requirements }}
- {{ .Resources }}

Additionally, the function {{ include "relative/path/to/file" }} allows for the inclusion of external files within the documentation. The {{ .Module }} variable is a special variable that represents the Terraform module struct itself, rather than a generated section.

An example of a custom content template demonstrates the flexibility of this system. The following configuration allows arbitrary text to be placed anywhere in the content, and sections can be reordered or interspersed with custom logic:

yaml content: |- Any arbitrary text can be placed anywhere in the content {{ .Header }} and even in between sections {{ .Providers }} and they don't even need to be in the default order {{ .Outputs }} include any relative files {{ include "relative/path/to/file" }} {{ .Inputs }} # Examples hcl
{{ include "examples/foo/main.tf" }}
## Resources {{ range .Module.Resources }} - {{ .GetMode }}.{{ .Spec }} ({{ .Position.Filename }}#{{ .Position.Line }}) {{- end }}

This level of control allows engineers to create documentation that matches the style and structure of their organization's technical writing standards, embedding code examples and resource listings directly from the module source.

Automation in CI/CD Pipelines

Ensuring that documentation is always up-to-date is best achieved through automation. terraform-docs integrates seamlessly with GitHub Actions and pre-commit hooks.

GitHub Actions

To use the terraform-docs GitHub Action, a YAML workflow file must be configured, typically located at .github/workflows/documentation.yml. The action can be triggered on pull requests and configured to push changes back to the PR. The following workflow demonstrates a standard configuration:

yaml name: Generate terraform docs on: - pull_request jobs: docs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 with: ref: ${{ github.event.pull_request.head.ref }} - name: Render terraform docs and push changes back to PR uses: terraform-docs/gh-actions@main with: working-dir: . output-file: README.md output-method: inject git-push: "true"

This workflow checks out the pull request branch, runs the documentation generator, and pushes the updated README.md back to the PR, ensuring that reviewers can see the documentation changes alongside the code changes.

Pre-commit Hooks

For local development, the pre-commit framework can enforce documentation updates before a commit is pushed. First, pre-commit must be installed. Then, a .pre-commit-config.yaml file is created in the root of the Git repository with the following content:

yaml repos: - repo: https://github.com/terraform-docs/terraform-docs rev: "v0.24.0" hooks: - id: terraform-docs-go args: ["markdown", "table", "--output-file", "README.md", "./mymodule/path"]

After configuring the file, the hooks are installed using:

bash pre-commit install pre-commit install-hooks

Once configured, any changes to the module's .tf files will automatically trigger an update to the documentation when a commit is attempted, preventing documentation drift at the source.

Library Usage and Programmatic Integration

While terraform-docs is primarily designed as a standalone binary, parts of its functionality are available as a Go library. This allows developers to integrate documentation generation into custom tools or larger infrastructure projects. The library exposes packages for formatting, printing, and Terraform module loading.

The following Go code example demonstrates how to build Terraform docs for a module root path and a provided content template:

```go
import (
"github.com/terraform-docs/terraform-docs/format"
"github.com/terraform-docs/terraform-docs/print"
"github.com/terraform-docs/terraform-docs/terraform"
)

func buildTerraformDocs(path string, tmpl string) (string, error) {
config := print.DefaultConfig()
config.ModuleRoot = path // module root path (can be relative or absolute)
module, err := terraform.LoadWithOptions(config)
if err != nil {
return "", err
}
// Generate in...
}
```

This API allows for programmatic control over the configuration, such as setting the module root and loading the module with specific options, enabling custom documentation pipelines that go beyond the standard command-line interface.

Conclusion

terraform-docs is an essential tool for modern Terraform engineering, bridging the gap between code and documentation. By automatically generating human-readable documentation from source files, it enforces a high standard of clarity and accuracy. The tool's support for multiple installation methods, including pre-compiled binaries, Go installs, Docker containers, and package managers, ensures it can be integrated into any development workflow. The robust configuration system, powered by YAML files and Go templates, allows for deep customization of the output format, enabling organizations to maintain a consistent documentation style across all modules. Furthermore, the integration with CI/CD pipelines through GitHub Actions and pre-commit hooks ensures that documentation remains synchronized with code changes, eliminating the risk of outdated or incorrect information. As infrastructure complexity continues to grow, the ability to auto-generate accurate documentation is not just a convenience but a critical requirement for scalable and maintainable infrastructure as code practices.

Sources

  1. terraform-docs/terraform-docs
  2. terraform-docs Installation Guide
  3. hashicorp/terraform

Related Posts