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

projesdmRcrokEtccdA//soD/gaarcMerpusEtcin..th/bymiioodimdntpoenlgeekpc-cnslista/odtupyearimnre.ett.yn-ematrddm.e.lmsmdpdonse.md

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()

Terraform Docs Generation

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
#`#ffffw#`rrrri#`dooootpimmmmhPyadlwwdaaytgddddDnbiinppthriiiiisttsiihoaaaaaa=hhonmggggg=adcnsrrrrrACpCba/aaaaaRLlilcDammmmmoBuu=hldcirssss(u(s=sebbaac..."t"ttRcghiaaaPeLe[eD=hrimwwwr5orErSeatpssso3a(C((Emeo...d(d"S""lascrcdnu"A(DPapttoaecDBp"aosiumtttNapAtstrDpawiSllPatieiuboo"aiI"gC.atarn)nc)rapgeskca1:ecyreAet"Shaiirri)Qemmimc"o,L(,pmph)n""opoi"E)RCrort)Celtrte:Sdutc(isEAt"stCRLuA"eSDBrP)rS,eI,"R,2Eo"lus)ath,seot5wEi3=CCFSaa(cl"hsAeeP,If3i"l)e]name="docs/images/architecture"):

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

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##--#`#kk##`#r###t###`uu#`RiATP#e##WW#`CbbRmuOePcoePp#hh#boeeuTanvfrcorrNsIaaamccnerbeeelmoonSttDsmttbmkordrssicrfcyihallopdovees:semoimauanolokisqsdardplsgdglkaw:ecut[iuleteenseotnwrioCorrnorrotgSe[is:LneOotmtsststSpiIsspussiopaett[:etRrso-nriestriefeddldvosyo[ansipisainsorteproaarctleioerg-pdeoesqotntnlps/fm,unaso=Psisesasrwdrkepeohnaesprcaesd.=vetehsisdbrecsteoorehdalvNi]reiasdsc-ms]eser]i]unncbeo=o5kmcovers.

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
#1234567D#.......ocBue*******msDATVMKDetoueeaeinctsrkeatPsotsepgarmirtalayoiraicitontumotveunsniertsbcgoeoaiewedgaosssinoerkntectcsc'hrshhota*eacdct*rbueaoi*lr*do*er*cen*eh**L*no**itrnD*Meko*ecGriSAcsomtaPhoa'mIeadUisecnpddkdsd/aroieaDecncatippsgorearo,dcgo,fehardrcfausoosstmcamdhaestmeivr.ecpebovseeTPdatvarRelohete,,igoradeuyitsRatrmaEthsiaimAiengteDorceMniwrE,dfieeitvfsnlhirpteeoestwmchetveasmlapimldeaattreiisognorasyourcode,andit'llactuallygetread.