
Automating infrastructure documentation
Hand-written docs start lying after about three weeks. Generating the inventory from the source of truth is cheaper than maintaining it.
Every hand-written infrastructure document is wrong. Not “possibly wrong”: wrong. The only real question is how long it takes: in my case, roughly three weeks.
The problem isn’t laziness
Everyone documents carefully at go-live. Then a VM gets resized on a Tuesday evening, a firewall rule is added during an on-call, a service moves elsewhere. None of those actions come with a wiki update, because the wiki isn’t on the critical path.
The conclusion I draw from that: anything that isn’t generated isn’t trustworthy.
Generate from the source of truth
The Ansible inventory already knows which machines exist and which roles they carry. Use it to produce the page people will actually read.
- name: Generate the documented inventory
hosts: localhost
gather_facts: false
tasks:
- name: Render the host table
ansible.builtin.template:
src: templates/inventory.md.j2
dest: docs/inventory.md
mode: '0644'
The template stays short on purpose: it only formats facts that were already collected:
# Inventory
| Host | IP | OS | Roles |
| ---- | -- | -- | ----- |
{% for host in groups['all'] | sort %}
| {{ host }} | {{ hostvars[host].ansible_host }} | {{ hostvars[host].os_family }} | {{ hostvars[host].roles | join(', ') }} |
{% endfor %}
Enforce it in CI
Generating is good; proving it’s current is better. A CI job regenerates the docs and fails if the result differs from what’s committed:
docs:check:
stage: test
script:
- ansible-playbook playbooks/docs.yml
- git diff --exit-code docs/
The failure message becomes explicit: the documentation no longer matches the inventory, regenerate it. It stops being a matter of individual discipline and becomes a pipeline constraint.
What still has to be written by hand
The generator produces facts: which machines, which addresses, which roles. It does not produce intent: why this service sits alone on its own VLAN, why that database isn’t replicated, what to do when a given alert fires.
That part gets written by a human, and it ages far better: reasons change much more slowly than IP addresses.