Documentation that lives outside your codebase gets stale. Documentation that isn’t tested breaks. Let’s treat docs like code—versioned, automated, and verified.
Docs Live with Code# Repository Structure# p ├ ├ │ │ │ │ │ │ │ ├ └ r ─ ─ ─ ─ o ─ ─ ─ ─ j e s d ├ ├ ├ │ └ m R c r o ─ ─ ─ ─ k E t c c ─ ─ ─ ─ d A / / s o D / g a a └ r ├ └ c M e r p ─ u ─ ─ s E t c i ─ n ─ ─ . . t h / b y m i i o o d i m d n t p o e n l g e e k p c - c n s l i s t a / o d t u p y e a r i m n r e . e t t . y n - e m a t r d d m . e . l m s m d p d o n s e . m d
MkDocs Configuration# 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# mkdocs.yml
site_name : My Project
site_url : https://docs.example.com
repo_url : https://github.com/org/project
theme :
name : material
features :
- navigation.tabs
- navigation.sections
- search.highlight
- content.code.copy
plugins :
- search
- git-revision-date-localized
- macros
markdown_extensions :
- admonition
- codehilite
- toc :
permalink : true
- pymdownx.superfences :
custom_fences :
- name : mermaid
class : mermaid
format : !!python/name:pymdownx.superfences.fence_code_format
nav :
- Home : index.md
- Getting Started : getting-started.md
- Architecture : architecture.md
- API Reference : api/
- Runbooks :
- Deployment : runbooks/deployment.md
- Incidents : runbooks/incident-response.md
Auto-Generated API Docs# From OpenAPI Spec# 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# docs/api/openapi.yaml
openapi : 3.0.3
info :
title : My API
version : 1.0.0
description : |
API for managing resources.
## Authentication
All endpoints require Bearer token authentication.
paths :
/users :
get :
summary : List users
tags : [ Users]
parameters :
- name : limit
in : query
schema :
type : integer
default : 20
responses :
'200' :
description : List of users
content :
application/json :
schema :
type : array
items :
$ref : '#/components/schemas/User'
components :
schemas :
User :
type : object
properties :
id :
type : string
format : uuid
email :
type : string
format : email
created_at :
type : string
format : date-time
Generate from Code# 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# generate_docs.py
from fastapi import FastAPI
from fastapi.openapi.utils import get_openapi
import json
import yaml
app = FastAPI ()
# ... your routes ...
def export_openapi ():
"""Export OpenAPI spec to file."""
openapi_schema = get_openapi (
title = app . title ,
version = app . version ,
description = app . description ,
routes = app . routes ,
)
# Write JSON
with open ( 'docs/api/openapi.json' , 'w' ) as f :
json . dump ( openapi_schema , f , indent = 2 )
# Write YAML
with open ( 'docs/api/openapi.yaml' , 'w' ) as f :
yaml . dump ( openapi_schema , f , default_flow_style = False )
if __name__ == "__main__" :
export_openapi ()
1
2
3
4
5
# Install terraform-docs
brew install terraform-docs
# Generate markdown from module
terraform-docs markdown table ./modules/vpc > ./modules/vpc/README.md
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# .terraform-docs.yml
formatter : markdown table
sections :
show :
- requirements
- providers
- inputs
- outputs
- resources
output :
file : README . md
mode : inject
template : |-
<!-- BEGIN_TF_DOCS -->
{{ . Content }}
<!-- END_TF_DOCS -->
Diagrams as Code# Mermaid in Markdown# 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# Architecture
```mermaid
graph TB
subgraph "Public"
LB[Load Balancer]
end
subgraph "Application Tier"
API1[API Server 1]
API2[API Server 2]
end
subgraph "Data Tier"
DB[(PostgreSQL)]
Cache[(Redis)]
end
LB --> API1
LB --> API2
API1 --> DB
API2 --> DB
API1 --> Cache
API2 --> Cache
# ` # f f f f w # ` r r r r i # ` d o o o o t p i m m m m h P y a d l w w d a a y t g d d d d D n b i i n p p t h r i i i i i s t t s i i h o a a a a a a = h h o n m g g g g g = a d c n s r r r r r A C p C b a / a a a a a R L l i l c D a m m m m m o B u u = h l d c i r s s s s ( u ( s = s e b b a a c . . . " t " t t R c g h i a a a P e L e [ e D = h r i m w w w r 5 o r E r S e a t p s s s o 3 a ( C ( ( E m e o . . . d ( d " S " " l a s c r c d n u " A ( D P a p t t o a e c D B p " a o s i u m t t t N a p A t s t r D p a w i S l l P a t i e i u b o o " a i I " g C . a t a r n ) n c ) r a p g e s k c a 1 : e c y r e A e t " S h a i i r r i ) Q e m m i m c " o , L ( , p m p h ) n " " o p o i " E ) R C r o r t ) C e l t r t e : S d u t c ( i s E A t " s t C R L u A " e S D B r P ) r S , e I , " R , 2 E o " l u s ) a t h , s e o t 5 w E i 3 = C C F S a a ( c l " h s A e e P , I f 3 i " l ) e ] n a m e = " d o c s / i m a g e s / a r c h i t e c t u r e " ) : CI/CD for Documentation# GitHub Actions# 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# .github/workflows/docs.yml
name : Documentation
on :
push :
branches : [ main]
paths :
- 'docs/**'
- 'mkdocs.yml'
- 'src/**/*.py' # Regenerate if code changes
pull_request :
paths :
- 'docs/**'
jobs :
build :
runs-on : ubuntu-latest
steps :
- uses : actions/checkout@v4
with :
fetch-depth : 0 # For git-revision-date plugin
- uses : actions/setup-python@v5
with :
python-version : '3.11'
- name : Install dependencies
run : |
pip install mkdocs-material mkdocs-macros-plugin
pip install -e . # Install project for API doc generation
- name : Generate API docs
run : python generate_docs.py
- name : Generate Terraform docs
run : |
terraform-docs markdown table ./terraform > ./docs/terraform.md
- name : Build documentation
run : mkdocs build --strict
- name : Upload artifact
uses : actions/upload-pages-artifact@v3
with :
path : site/
deploy :
if : github.ref == 'refs/heads/main'
needs : build
runs-on : ubuntu-latest
permissions :
pages : write
id-token : write
environment :
name : github-pages
steps :
- uses : actions/deploy-pages@v4
Documentation Testing# Link Checking# 1
2
3
4
5
6
# In CI
- name : Check links
uses : lycheeverse/lychee-action@v1
with :
args : --verbose --no-progress './docs/**/*.md'
fail : true
Code Example Testing# 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# test_docs.py
import subprocess
import re
from pathlib import Path
def extract_code_blocks ( markdown_file : Path ) -> list :
"""Extract fenced code blocks from markdown."""
content = markdown_file . read_text ()
pattern = r '```(\w+)\n(.*?)```'
return re . findall ( pattern , content , re . DOTALL )
def test_python_examples ():
"""Test that Python code examples are valid."""
for doc in Path ( 'docs' ) . rglob ( '*.md' ):
for lang , code in extract_code_blocks ( doc ):
if lang == 'python' :
# Check syntax
try :
compile ( code , doc . name , 'exec' )
except SyntaxError as e :
raise AssertionError ( f "Syntax error in { doc } : { e } " )
def test_bash_examples ():
"""Test that bash commands are valid syntax."""
for doc in Path ( 'docs' ) . rglob ( '*.md' ):
for lang , code in extract_code_blocks ( doc ):
if lang in ( 'bash' , 'shell' , 'sh' ):
result = subprocess . run (
[ 'bash' , '-n' , '-c' , code ],
capture_output = True
)
if result . returncode != 0 :
raise AssertionError (
f "Bash syntax error in { doc } : { result . stderr . decode () } "
)
OpenAPI Validation# 1
2
3
4
# In CI
- name : Validate OpenAPI spec
run : |
npx @apidevtools/swagger-cli validate docs/api/openapi.yaml
README Generation# From Template# 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# generate_readme.py
from jinja2 import Template
import subprocess
def get_version ():
return subprocess . check_output (
[ 'git' , 'describe' , '--tags' , '--always' ]
) . decode () . strip ()
def get_contributors ():
output = subprocess . check_output (
[ 'git' , 'shortlog' , '-sn' , 'HEAD' ]
) . decode ()
return [ line . split ( ' \t ' )[ 1 ] for line in output . strip () . split ( ' \n ' )]
template = Template ( '''
# {{ name }}
{{ description }}
## Installation
```bash
pip install {{ package_name }}
Quick Start# 1
2
3
4
from {{ package_name }} import Client
client = Client ( api_key = "your-key" )
result = client . do_something ()
Documentation# Full documentation at [{{ docs_url }}]({{ docs_url }})
Version# Current version: {{ version }}
Contributors# {% for contributor in contributors %}
{{ contributor }}
{% endfor %}
‘’’) readme = template.render(
name=“My Project”,
description=“A helpful library”,
package_name=“myproject”,
docs_url=“https://docs.example.com ”,
version=get_version(),
contributors=get_contributors()
)
with open(‘README.md’, ‘w’) as f:
f.write(readme)
# # ` # # B # - - - # # S # # - - # ` # k k # # ` # r # # # t # # # ` u u # ` R i A T P # e # # W W # ` C b b R m u O e P c o e P p # h h # b o e e u T a n v f r c o r r N s I a a a m c c n e r b e e e l m o o n S t t D s m t t b m k o r d r s s i c r f c y i h a l l o p d o v e e s : s e m o i m a u a n o l o k i s q s d a r d p l s g d g l k a w : e c u t [ i u l e t e e n s e o t n w r i o C o r r n o r r o t g S e [ i s : L n e O o t m t s s t s t S p i I s s p u s s i o p a e t t [ : e t R r s o - n r i e s t r i e f e d d l d v o s y o [ a n s i p i s a i n s o r t e p r o a a r c t l e i o e r g - p d e o e s q o t n t n l p s / f m , u n a s o = P s i s e s a s r w d r k e p e o h n a e s p r c a e s d . = v e t e h s i s d b r e c s t e o o r e h d a l v N i ] r e i a s d s c - m s ] e s e r ] i ] u n n c b e o = o 5 k m c o v e r s . Resolution# Step-by-step fix.
Escalation# When and who to escalate to.
Rollback# How to undo changes.
Link to architecture docs Link to monitoring dashboard Link to other runbooks # 1 2 3 4 5 6 7 D # . . . . . . . o c B u e * * * * * * * m s D A T V M K D e t o u e e a e i n c t s r k e a t P s o t s e p g a r m i r t a l a y o i r a i c i t o n t u m o t v e u n s n i e r t s b c g o e o a i e w e d g a o s s s i n o e r k n t e c t c s c ' h r s h h o t a * e a c d c t * r b u e a o i * l r * d o — * e r * c e n * e h * * L — * n — o * * i t r n D — * M e — — k o * e — c G r i S A c s o — m t a P h o a ' m I e a d U i s e c n p d d k d s d / a r o i e a D e c n c a t i p p s g o r e a r o , d c g o , f e h a r d r c f a u s o o s s t m c a m d h a e s t m e i v r . e c p e b o v s e e T P d a t v a r R e l o h e t e , , i g o r a d e u y i t s R a t r m a E t h s i a i m A i e n g t e D o r c e M n i w r E , d f i e e i t v f s n l h i r p t e e o e s t w m c h e t v e a s m l a p i m l d e a a t t r e i i s o g n o r a s y o u r c o d e , a n d i t ' l l a c t u a l l y g e t r e a d .