From the Desk of Doc Holiday >

How to Automate Infrastructure Change Documentation From Terraform Plans

Extract Terraform plan data automatically and convert it into searchable, audit-ready change records. Learn to parse plan JSON, redact secrets, and track infrastructure changes programmatically.
August 3, 2026
The Doc Holiday Team
How to Automate Infrastructure Change Documentation From Terraform Plans

It is late Friday afternoon, and an infrastructure pull request sits in the queue. The Terraform plan output shows an autoscaling group adjustment, a database version bump, and some security group edits. The engineer who opened the request takes a screenshot of the terminal, pastes it into a Jira ticket, types a quick sentence about scaling up for the weekend, and clicks submit.

The deployment succeeds. The change record, however, is already dead. A screenshot of a terminal is not searchable. It cannot be audited programmatically. It will not help the support manager who needs to know exactly which security group changed when a connection fails on Monday morning. The information was there in the plan, but the manual process of recording it turned precise engineering data into an unusable artifact.

To maintain infrastructure documentation that actually scales, you have to stop asking engineers to write down what they just did. You have to extract the change directly from the Terraform plan, translate it programmatically into a human-readable format, and pair it with the deployment metadata that proves when and why it happened.

Engineer taking screenshot of terminal while filing cabinets overflow behind them
The artifact survives longer than its usefulness.

The Plan Already Knows More Than the Screenshot

When Terraform runs a plan, it refreshes the state of known remote objects, compares the current configuration against that prior state, and proposes a set of actions. If you run terraform plan -out=tfplan, you capture those proposed actions in a saved binary file.

That file contains everything an auditor or platform engineer needs to know. It is not, however, built for human consumption. Plain-text output is a decent review surface and a terrible data interface; to automate documentation, convert the saved plan to a machine-readable format.

terraform plan -out=tfplan
terraform show -json tfplan > plan.json
terraform providers schema -json > provider-schema.json

The resulting JSON output exposes the internal structure of the proposed change. It includes the resource_changes array, which lists the absolute address, the provider type, and the specific actions (create, update, or delete) for every resource. It also includes the before and after values for the resource attributes, and a replace_paths array that tells you exactly which attribute change forced a resource replacement.

This structured data is the foundation of an automated documentation pipeline. It removes the need for a human to manually transcribe what changed.

Raw plan JSON is not a finished change record. It often contains cleartext sensitive values, such as database passwords or API tokens, even if they were obscured in the terminal output. The JSON also lacks the broader context of why the change was requested.

Turn a Diff Into a Change Record People Can Use

A useful infrastructure change record needs to answer four questions: who requested it, what resources changed, why it was needed, and what the blast radius was. To build this programmatically, a documentation pipeline must parse the plan JSON and map its technical fields to human-readable concepts.

Normalized evidenceHuman-readable record fieldHandling rule
Resource address, module, provider/typeScope and ownerMap prefixes and tags to the service catalog.
Actions, action_reason, replace_pathsChange class and risk cueCall out replacements and destroys.
Meaningful before/after pathsWhat changedRender labels from the provider schema.
PR/ticket, actor, approval, commitWho requested it and whyTake from CI metadata and ticket integration.
Graph neighbors, remote-state inputsBlast radius and cross-stack scopeFlag declared dependencies.

The parser should iterate through the resource_changes array, ignoring no-op changes. For resources that are being modified, it should compare the before and after objects to extract the specific attributes that shifted. It must respect sensitive markers before the record is written.

To make the output readable, the pipeline can use the provider schema JSON to translate terse attribute names into clear descriptions. It uses the schema to correctly navigate nested blocks like list or set. It should also preserve after_unknown values as "known after apply," rather than attempting to guess the final state.

The pipeline can then enrich this resource-level data with blast-radius context. Terraform builds a dependency graph to determine execution order. Analyze that graph for Terraform-visible neighbors, then record the source stack, destination stack, and specific remote-state outputs whenever a change consumes or publishes them.

That distinction matters. A plan sees the dependencies declared in the evaluated configuration; it does not prove that an independently deployed stack, a manually maintained DNS record, or an application setting has no relationship to the change. Mark those relationships as unverified context and send the record to review instead of manufacturing a complete blast radius. Infrastructure has enough imaginative fiction already.

Automation reliably handles the "what." It extracts the resource changes, redacts the secrets, and formats the output. It cannot invent the "why." That is where human review enters the workflow.

The Documentation Should Arrive With the Deployment

A compliant change-management process requires documentation of the change, its approval, and its deployment timeline. If the documentation is generated days after the deployment, or if it is based on a speculative plan that was never applied, the audit trail is broken.

The documentation must arrive with the deployment. A standard continuous integration and continuous deployment (CI/CD) pipeline can orchestrate this.

A Git hook can give an author a local preview from a plan file. Treat that as a convenience, not canonical evidence. The authoritative record begins in the controlled pipeline, where the environment, commit, policy results, approvals, and final apply outcome can all travel with it.

When a pull request is opened, a CI job runs the Terraform plan, generates the JSON, and triggers the documentation parser. The parser produces a redacted, human-readable draft of the change record. This draft is attached to the pull request as a workflow artifact.

This is the review gate. Version bumps, autoscaling adjustments, and standard resource additions can usually move through a lightweight review lane. Major networking shifts, security-policy updates, replacement-heavy plans, destroys, and cross-region migrations deserve a platform engineer or site reliability engineer. The reviewer validates the automated interpretation of the blast radius, adds the business context explaining why the change is necessary, and approves the request.

When the code is merged and the apply job succeeds, the pipeline fires a webhook or repository dispatch event. This event carries the finalized change record, the commit SHA, the applied-plan identifier, the timestamp, and the deployment outcome to a centralized documentation repository.

The change record is published only after the deployment is confirmed. It is an engineered delivery trail, not an after-the-fact writing project.

Four-stage workflow diagram: pull request, CI plan, parser, and review approval
Documentation surfaces in the pipeline, not in the aftermath.

Doc Holiday generates infrastructure changelogs and release documentation directly from engineering workflows like Terraform plans. It provides the structure for teams to validate and manage that output at scale, which is especially useful for lean platform teams that still need audit-ready documentation.

time to Get your docs in a row.

Begin your free trial and and start your Doc Holiday today!