Skip to content

Customizing Documentation Standards

Overview

ThothCTL generates IaC documentation using terraform-docs under the hood. You can use the built-in standards as-is, or provide a custom configuration file to control exactly what gets generated.

Prerequisites

terraform-docs must be installed and available in your PATH:

# macOS
brew install terraform-docs

# Linux
curl -sSLo ./terraform-docs.tar.gz https://terraform-docs.io/dl/v0.18.0/terraform-docs-v0.18.0-$(uname)-amd64.tar.gz
tar -xzf terraform-docs.tar.gz
chmod +x terraform-docs
sudo mv terraform-docs /usr/local/bin/

# Verify
terraform-docs --version

Built-in Documentation Standards

ThothCTL ships with two documentation standards, selected via --mood:

stacks (default)

Designed for full infrastructure projects (Terragrunt stacks, root modules).

thothctl document iac -f terragrunt --mood stacks

Characteristics: - Output mode: replace (overwrites entire README) - Includes dependency graph SVG reference - Shows: header, requirements, providers, modules, resources, inputs, outputs - Sorted by: name

Generated README structure:

<!-- BEGIN_TF_DOCS -->
# Module Header (from main.tf)

## Code Dependencies Graph
![Graph](./graph.svg)

## Requirements
## Providers
## Modules
## Resources
## Inputs
## Outputs
<!-- END_TF_DOCS -->

modules

Designed for reusable Terraform modules with examples.

thothctl document iac -f terraform --mood modules

Characteristics: - Output mode: inject (preserves content outside markers) - Includes inline example from examples/complete/main.tf - Shows: header, requirements, providers, modules, resources, inputs, outputs - Sorted by: required - Adds "DO NOT EDIT" notice

Generated README structure:

# Your custom content here (preserved)

<!-- BEGIN_TF_DOCS -->
# Module Header

## Example
```hcl
// Content of examples/complete/main.tf inlined here
```

## Requirements
## Providers
## Inputs
## Outputs
<!-- END_TF_DOCS -->

# More custom content (preserved)

Note: resources is a backward-compatible alias for stacks.

Metadata in Terraform Files

terraform-docs extracts metadata from your .tf files to populate the documentation. Understanding where content comes from helps you write better docs.

Header (module description)

The header-from config option (default: main.tf) pulls the first comment block at the top of that file:

/**
 * # My Networking Module
 *
 * Creates a VPC with public and private subnets across multiple
 * availability zones. Includes NAT gateways and route tables.
 *
 * ## Features
 * - Multi-AZ deployment
 * - Configurable CIDR ranges
 * - Optional VPN gateway
 */

resource "aws_vpc" "this" {
  # ...
}

This becomes the {{ .Header }} content in the generated README.

Similarly, footer-from pulls content from a separate file (e.g., docs/footer.md):

# terraform-docs.yml
header-from: main.tf
footer-from: docs/footer.md

Variable descriptions

Input/output tables are built from description attributes:

variable "vpc_cidr" {
  description = "CIDR block for the VPC. Must be /16 or larger."
  type        = string
  default     = "10.0.0.0/16"

  validation {
    condition     = can(cidrhost(var.vpc_cidr, 0))
    error_message = "Must be a valid CIDR block."
  }
}

variable "environment" {
  description = "Deployment environment (dev, staging, prod)."
  type        = string
}

output "vpc_id" {
  description = "ID of the created VPC."
  value       = aws_vpc.this.id
}

Generated table:

Name Description Type Default Required
vpc_cidr CIDR block for the VPC. Must be /16 or larger. string "10.0.0.0/16" no
environment Deployment environment (dev, staging, prod). string n/a yes

Best practices for tf metadata

  • Always add description to every variable and output — empty descriptions produce blank table cells
  • Use the top comment block in main.tf for module-level documentation (purpose, features, usage notes)
  • Keep descriptions concise — one line; use the header comment block for extended explanations
  • Add type explicitly — terraform-docs renders the type column from it
  • Set default where appropriate — it shows users what's optional vs required

README Markers (Required)

Your README.md must contain these markers for documentation injection to work:

<!-- BEGIN_TF_DOCS -->
<!-- END_TF_DOCS -->

Without these markers, terraform-docs cannot determine where to write the output and will silently skip the file.

For new projects, add them to your README template:

# My Module

Description of what this module does.

<!-- BEGIN_TF_DOCS -->
<!-- END_TF_DOCS -->

Custom Configuration File

To fully customize the documentation output, create a terraform-docs.yml and pass it with --config-file:

thothctl document iac -f terraform --config-file ./terraform-docs.yml --recursive

When --config-file is provided, it completely overrides the built-in standard. ThothCTL passes it directly to terraform-docs.

Minimal Custom Config

formatter: "markdown table"

sections:
  show:
    - requirements
    - inputs
    - outputs

output:
  file: "README.md"
  mode: inject
  template: |-
    <!-- BEGIN_TF_DOCS -->
    {{ .Content }}
    <!-- END_TF_DOCS -->

sort:
  enabled: true
  by: required

Full Custom Config (all options)

formatter: "markdown table"

# Pull header text from this file
header-from: main.tf
footer-from: ""

# Which sections to include
sections:
  hide: []
  show:
    - header
    - requirements
    - providers
    - modules
    - resources
    - inputs
    - outputs
    - footer

# Custom content template with Golang template syntax
content: |-
  {{ .Header }}

  ## Architecture

  ![Architecture](./docs/architecture.png)

  ## Usage

  ```hcl
  module "this" {
    source = "git::https://github.com/myorg/this-module.git?ref=v1.0.0"

    // Required
    name        = "example"
    environment = "dev"
  }
  ```

  {{ .Requirements }}
  {{ .Providers }}
  {{ .Modules }}
  {{ .Resources }}
  {{ .Inputs }}
  {{ .Outputs }}
  {{ .Footer }}

# Output configuration
output:
  file: "README.md"
  mode: inject           # "inject" preserves content outside markers
                         # "replace" overwrites the entire file
  template: |-
    <!-- BEGIN_TF_DOCS -->
    {{ .Content }}
    <!-- END_TF_DOCS -->

# Sort inputs/outputs
sort:
  enabled: true
  by: required           # "required", "name", or "type"

# Display settings
settings:
  anchor: true
  color: true
  default: true
  description: false
  escape: true
  hide-empty: false
  html: true
  indent: 2
  lockfile: true
  required: true
  sensitive: true
  type: true

Key Configuration Options

Option Values Description
output.mode inject, replace inject preserves content outside markers; replace overwrites the file
sort.by name, required, type How to sort inputs and outputs
header-from filename File to extract the module description from (first comment block)
content Go template Full control over document structure using {{ .Section }} placeholders
sections.show list Which sections to include
sections.hide list Which sections to exclude

Available Template Placeholders

Placeholder Content
{{ .Header }} Module description from header-from file
{{ .Footer }} Content from footer-from file
{{ .Requirements }} Required Terraform and provider versions
{{ .Providers }} Provider configurations
{{ .Modules }} Child module calls
{{ .Resources }} Resources created
{{ .Inputs }} Input variables table
{{ .Outputs }} Output values table
{{ include "path" }} Inline content from a file

Per-Project Configuration

You can commit a .terraform-docs.yml in your project root. When using --config-file, point to it:

thothctl document iac -f terraform --config-file .terraform-docs.yml --recursive

Recommended project structure:

my-project/
├── .terraform-docs.yml    ← your custom standard
├── modules/
│   ├── networking/
│   │   ├── main.tf
│   │   └── README.md     ← generated
│   └── compute/
│       ├── main.tf
│       └── README.md     ← generated
└── stacks/
    └── prod/
        ├── main.tf
        └── README.md     ← generated

Exclude Patterns

The --exclude option uses substring matching (not glob). Any directory whose path contains the pattern is skipped:

# Exclude .terraform caches and example directories
thothctl document iac -f terraform --recursive \
  --exclude .terraform \
  --exclude examples \
  --exclude test

⚠️ --exclude modules would skip any directory with "modules" in the path. Be specific.

End-to-End Examples

Generate docs for a Terragrunt stack project

# Default standard (stacks) + SVG dependency graph
thothctl document iac -f terragrunt --recursive

# With mermaid graph instead
thothctl document iac -f terragrunt --recursive --graph-type mermaid

Generate docs for a reusable module

# Uses 'modules' standard — includes example code inline
thothctl document iac -f terraform --mood modules

Use organization-wide standard

# Store your standard in the Space templates directory
# then reference it in all projects:
thothctl document iac -f terraform --recursive \
  --config-file ~/.thothcf/spaces/production/templates/terraform-docs.yml

CI/CD: Auto-generate and commit

- name: Generate documentation
  run: |
    thothctl document iac -f terragrunt --recursive --graph-type mermaid
    git diff --quiet || git commit -am "docs: auto-update IaC documentation"

Troubleshooting

Issue Cause Solution
README not updated Missing <!-- BEGIN_TF_DOCS --> markers Add markers to your README.md
command not found: terraform-docs Not installed Install terraform-docs (see Prerequisites)
Empty output No .tf files in directory Ensure directory contains Terraform files
Sections missing Not listed in sections.show Add desired sections to your config
include fails File path relative to module root Use relative path from the documented directory