All articles

Documentation-as-Code: The Complete Playbook for Engineering Teams

Treat your docs like production code, and your team stops arguing about whose README is right. Here's the playbook engineering leaders actually use.

D

DocPilot Team

AI documentation research and engineering from Onzira Labs.

13 min read

Why Documentation-as-Code Is the Default Now, Not the Niche Choice

Three years ago, a survey of 400 engineering teams found that fewer than 1 in 4 kept their docs in the same repository as their code. By last year, that number had crossed 60% at companies with more than 50 engineers. The shift is not a fad. It is the predictable outcome of three converging pressures: faster release cycles, distributed teams, and the painful realization that a Confluence page last edited in 2021 is, for all practical purposes, fiction.

Docs-as-code means treating documentation with the same engineering rigor you apply to production systems. Markdown or similar text formats live next to the code in version control. Pull requests gate changes. CI pipelines build, lint, and deploy the output. Broken links fail the build. Stale pages get flagged by automated checks. A new engineer on day three can clone one repo and find everything they need to ship.

This playbook covers the full lifecycle: choosing a stack, structuring repositories, writing for review, automating quality, and building the cultural muscle that keeps the system alive. It is written for engineering leaders, senior developers, and founders who have been burned by docs that lie and want to fix the root cause, not the symptom.

What Documentation-as-Code Actually Means

The phrase gets thrown around loosely. A team that writes Markdown in a GitHub wiki but never opens a PR is not doing docs-as-code. They are doing wiki-markup-with-extra-steps. The real definition has five concrete properties.

The Five Properties of True Docs-as-Code

  1. Plain text source. Markdown, AsciiDoc, MDX, or reStructuredText. Anything diffable, greppable, and editable in any text editor. No proprietary WYSIWYG databases.
  2. Version controlled. Git. Same review process, same branch protection, same history as application code.
  3. Built and deployed by tooling. A static site generator or docs framework consumes the source and produces HTML, PDFs, or API references. The output is a deployable artifact.
  4. Subject to automated checks. Link validation, spell check, style lint, prose lint, code example execution, and freshness checks run in CI and block merges on failure.
  5. Owned by the same people who own the code. No external docs team gatekeeping. Engineers write, review, and maintain the pages that describe their systems.

If any of those five is missing, you have a hybrid setup that drifts back toward rot within a quarter.

The Spectrum from Wiki to Docs-as-Code

Most teams land somewhere on a spectrum rather than jumping to the extreme. A typical progression looks like this:

StageStorageReviewBuildCI ChecksOwner
0. WikiSaaS DBNoneNoneNoneAnyone
1. Wiki + Markdown exportMixedOptionalNoneNoneMixed
2. Git-hosted MarkdownGit repoPRManualNoneEngineers
3. Static site from repoGit repoPR requiredLocalLintEngineers
4. Full docs-as-codeGit repoPR + reviewAutomatedFull suiteEngineers

Stage 4 is the goal. Stages 1 and 2 are honest intermediate states, and many stop at 3 thinking they have arrived. Stage 3 still drifts because nobody automated the checks that matter.

Choosing Your Stack: Static Site Generators and Beyond

The tooling decision is less important than people think. What matters is whether the team can run it, debug it, and extend it without leaving their normal workflow. That said, a few options dominate in 2025 and are worth understanding before you commit.

The Mainstream Options

Docusaurus is the React-friendly default. It ships with versioned docs, i18n, a search box, and Algolia integration out of the box. If your team already lives in a React ecosystem and you need versioning for multiple product releases, Docusaurus is the lowest-friction choice. The plugin ecosystem is mature, and most things you need already exist.

MkDocs with the Material theme is the Python ecosystem's favorite and works equally well for any team that prefers simplicity over configuration. A single YAML file defines the navigation tree. Builds are fast. The Material theme is widely considered the best-looking open source docs theme available. For teams under 30 engineers who do not need MDX or React components, this is hard to beat.

Astro Starlight is the rising contender. It uses Astro's islands architecture, ships zero JS by default, and supports MDX with React, Vue, Svelte, or Solid components. If your team needs interactive examples, live API playgrounds, or design system docs with embedded components, Starlight is the modern answer.

Antora is purpose-built for multi-repo documentation. Each product team maintains their own docs repo, and Antora aggregates them into a single site with shared navigation and a unified search index. This is the right tool when you have four or more product repos that each need their own doc ownership.

How to Choose Without Paralysis

Pick based on three questions, in order:

  1. Single repo or many? Single repo and one product: Docusaurus or MkDocs. Many repos and many products: Antora.
  2. Does anyone need interactive components? Yes: Starlight or Docusaurus. No: MkDocs Material.
  3. What does the team already know? A team that already maintains a Gatsby or Next.js site will adopt Docusaurus faster than MkDocs, regardless of feature parity.

Spend at most one day on the decision. The migration cost between these generators is real but bounded, and you will want to migrate eventually anyway as requirements change. Resist the temptation to build a custom solution.

Structuring Your Repository

How you organize the files determines whether the system scales or collapses under its own weight. The structure below has been refined across dozens of engineering organizations and works from 5 engineers to 500.

The Recommended Layout

docs/
├── README.md                  # Repo overview, how to run docs locally
├── docs/
│   ├── getting-started/
│   │   ├── installation.md
│   │   ├── quickstart.md
│   │   └── concepts.md
│   ├── guides/
│   │   ├── authentication.md
│   │   ├── deployment.md
│   │   └── troubleshooting.md
│   ├── reference/
│   │   ├── api.md
│   │   ├── cli.md
│   │   └── config.md
│   └── architecture/
│       ├── overview.md
│       ├── data-flow.md
│       └── decisions/
│           ├── 0001-postgres-for-primary.md
│           └── 0002-event-driven-sync.md
├── .vale.ini                  # Prose linter config
├── .github/
│   └── workflows/
│       └── docs-ci.yml        # Build, lint, link check, deploy
└── mkdocs.yml or docusaurus.config.ts

Why This Structure Works

The four top-level folders correspond to the four mental modes when seeking information.

  • Getting started is read once per person, then forgotten. Short, linear, and never edited unless the install path changes.
  • Guides are task-oriented. "How do I deploy?" "How do I authenticate?" These pages reference reference material rather than duplicating it.
  • Reference is exhaustive and lookup-driven. API endpoints, CLI flags, config keys. These should be auto-generated from code wherever possible.
  • Architecture captures the why. Decision records live here, alongside diagrams and system overviews. This folder ages the best because it is written for the next engineer, not the next user.

Auto-Generated References

Manual API documentation is a leading indicator of drift. If a developer has to copy a curl example into a Markdown file by hand, that example will be wrong within a month. Generate it from the source instead.

For OpenAPI specs, tools like Swagger Codegen, Redocly CLI, or openapi-typescript turn your spec into HTML or typed clients. For GraphQL, GraphQL Code Generator produces reference docs from your schema. For CLI tools, the standard pattern is a --help scrape piped into a build step. The rule is simple: anything that is structured data in code should produce structured data in docs, not free-form prose.

# Example: docs/.github/workflows/docs-ci.yml
name: Docs CI
on:
  pull_request:
    paths: ['docs/**', '**/*.md']
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm ci
      - run: npm run lint:links
      - run: npm run lint:prose
      - run: npm run build

Writing for Review: Style, Tone, and Structure

Docs are read differently than code. A reader scans, jumps, and abandons within 30 seconds if the page does not deliver what the heading promised. This changes how you write and what you review for.

The Voice That Scales

Write in second person, present tense, active voice. "Run the migrate command" not "the migrate command can be run." Avoid hedging language like "might," "perhaps," and "depending on your configuration" unless the variability is real. If the variability is real, name the variables.

Keep paragraphs short. Two to four sentences is the ceiling for prose paragraphs. Lists and tables are preferred for anything with more than three items. Code blocks should be complete and runnable. Never paste a snippet that references variables the reader has not seen.

The Review Checklist

When reviewing a docs PR, evaluate five things in order.

  1. Does it answer the question in the heading? A page titled "Authentication" that spends three paragraphs on architecture has failed its first reader.
  2. Is the example runnable? Copy the code block. Paste it. Does it work on a clean checkout? If not, the PR is not ready.
  3. Are the links valid? Relative links to other Markdown files must resolve. Links to external sites should resolve. Automated link checkers catch most of this, but reviewers should spot-check.
  4. Does it introduce jargon without defining it? Every term that is not in the reader's assumed vocabulary should be linked to its definition on first appearance.
  5. Does it duplicate content that exists elsewhere? If yes, replace it with a link to the canonical source.

Setting Up a Prose Linter

Prose linters catch the failures humans miss in review fatigue. Vale is the strongest option because it enforces a style guide across the whole repo. Start with a published style like the Google developer docs style or write a minimal internal one covering your team's most common mistakes.

A starting .vale.ini might look like this:

StylesPath = styles
MinAlertLevel = error

[*.md]
BasedOnStyles = Google

Google.Passive = error
Google.Weasel = warning
Google.Colloquialisms = error

Run Vale in CI. Fail the build on errors. Warnings stay as warnings for the first month while everyone calibrates, then upgrade them to errors once the noise has died down.

Automating Quality: The CI Pipeline That Catches Rot

A docs build pipeline without quality gates is just a publishing tool. The point of automation is to make lying impossible. Five gates matter.

Gate 1: Link Validation

Every internal link must resolve. Every external link must return 200. Tools: lychee for Markdown link checking, htmltest for built HTML, or a custom Node script if you want fine control. Run on every PR and on a nightly cron for the external link check.

Gate 2: Prose Linting

As covered above. Vale, write-good, or a custom ruleset. The goal is to remove the mechanical feedback from human review so reviewers can focus on substance.

Gate 3: Spell Check

CSpell or codespell. Skip false positives with a project dictionary. The cost of a misspelled word in production docs is small individually but large in aggregate: it signals that nobody cared.

Gate 4: Code Example Execution

For any code block tagged as executable, run it. Tools like markdown-code-runner or doctest-style runners for your language catch the case where someone wrote an example, the API changed, and nobody noticed. Start small: cover the getting-started quickstart first, then expand as the suite matures.

Gate 5: Freshness Checks

Pages that have not been edited in 18 months are surfaced for review. The implementation is straightforward: a script walks the docs tree, finds files with git log older than the threshold, and writes a report that gets attached to a weekly digest or posted as a Slack message. Owners then either update the page or add a "Last verified" date with a justification.

Composing the Pipeline

# Combined docs CI
jobs:
  quality:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: lycheeverse/lychee-action@v2
        with:
          args: --fail docs/**/*.md
      - run: vale docs/
      - run: cspell --no-progress "docs/**/*.md"
      - run: npm run test:code-examples
      - run: |
          node scripts/freshness-report.js \
            --max-age-days 540 \
            --output artifacts/freshness.md
      - uses: actions/upload-artifact@v4
        with:
          name: freshness-report
          path: artifacts/freshness.md

The freshness report is intentionally not a hard failure. Soft signals need soft enforcement, or engineers learn to game the system.

Building the Culture: Why Most Docs-as-Code Initiatives Fail

The tooling is the easy part. Culture is what kills the program. Three failure modes account for most abandoned docs initiatives.

Failure Mode 1: Docs as Afterthought

The team treats docs as a checkbox on the PR template. The checkbox gets checked even when the docs are empty or wrong. The fix is to make the docs check meaningful.

In your PR template, replace "Add docs" with a structured prompt:

### Documentation impact

- [ ] No docs change needed (no behavior or interface change)
- [ ] Updated existing page: <link>
- [ ] Added new page: <link>
- [ ] Updated auto-generated reference via <tool/command>

The reviewer is then expected to verify that the change actually addresses one of those four outcomes. This shifts the work from checkbox theater to substantive review.

Failure Mode 2: Docs Owned by Nobody

When every team owns the docs, no team owns the docs. The fix is to designate a docs maintainer per repo, not as a full-time role but as a 5% responsibility with explicit time allocation. The maintainer's job is to triage the freshness report, chase stale pages, and curate the information architecture. Without this role, the rot compounds.

Failure Mode 3: Docs Written, Never Read

The team writes excellent docs that nobody opens. The fix is to measure and respond. Track search queries, page views, and feedback widget responses. If a page has zero views in three months, either it is wrong or it is irrelevant. Investigate.

What Good Looks Like

A healthy docs-as-code culture shows up in three observable signals.

  1. PRs include docs by default. New engineers ask "what page describes this?" without prompting.
  2. Reviews catch missing docs before merge. Reviewers treat docs gaps as merge blockers, equivalent to missing tests.
  3. Freshness reports get acted on. Stale pages get updated or formally deprecated with a redirect, not ignored.

None of these are about writing more. They are about treating docs as part of the product, not a separate artifact.

Metrics That Matter (and the Ones That Lie)

You will be asked how the docs program is doing. Resist the temptation to report vanity metrics.

Metrics Worth Tracking

  • Time from PR open to docs merged. A proxy for whether docs are considered part of the work or bolted on later.
  • Percentage of PRs that touch a doc file. The leading indicator. If less than 30% of behavior-changing PRs include a docs change, the culture has not taken.
  • Freshness report closure rate. Of pages flagged stale, how many are updated within 30 days.
  • Search-to-page ratio in feedback widgets. A high ratio of unhelpful searches indicates the IA is wrong.

Metrics to Ignore

  • Total page count. More pages is not better. Obsolete pages cost more than they are worth.
  • Total words written. Words are an input, not an outcome.
  • Time on page. A high time on page often indicates confusion, not comprehension.

The point of measuring is to drive decisions. Pick metrics that change behavior when they move.

Common Anti-Patterns to Avoid

A short list of patterns that consistently produce rot:

  • The mega README. A single README longer than 500 lines that tries to be both landing page and reference manual. Split it.
  • The orphan doc. A page that references a component, service, or feature that no longer exists. Delete it or redirect it.
  • The tutorial with a broken step. Tutorial pages that skip a step the reader needs. Tutorials must be runnable end-to-end on a clean environment.
  • The reference without examples. An API reference that lists every parameter and shows none. Always include a minimal working example.
  • The architecture diagram in a tool that exports to PDF. Diagrams are code. Keep the source in the repo (draw.io, Mermaid, PlantUML) and regenerate the export.

The First 30 Days: A Realistic Rollout

If you are starting from a wiki, do not try to migrate everything in week one. The 30-day plan below produces a working system that improves over time.

Week 1: Pick the generator. Stand up the repo. Move three pages: the README, the install guide, and one architecture diagram. Deploy to a staging URL.

Week 2: Set up the CI pipeline with link checking, Vale, and spell check. Make them required checks on PRs targeting main.

Week 3: Add the freshness report and post it to your team channel. Add the docs section to the PR template.

Week 4: Identify the docs maintainer per repo. Schedule a 30-minute monthly docs triage meeting to review the freshness report and route work.

After 30 days, the system is live and the muscle is starting to form. The next quarter is about expanding coverage, retiring the wiki, and tightening the feedback loop with readers.

Closing Thought

Docs-as-code is not a tooling choice. It is a discipline that says documentation is part of the product and should be engineered with the same care as the code it describes. The teams that get it right treat docs PRs with the same seriousness as feature PRs, automate the checks humans are bad at, and accept that perfect docs are less important than docs that are accurate today and can be fixed tomorrow.

If your team is buried under a wiki no one trusts and you want to start the move to a docs-as-code setup without spending weeks on the tooling decision, DocPilot connects to your GitHub repos, scans the codebase, and produces a first draft of READMEs, setup guides, architecture pages, and API references in minutes. You commit the output like any other code change, wire it into your existing CI, and your team owns it from day one. Start with one repo and expand from there.

Ship documentation that keeps up

Try DocPilot on your repository — free for one repo.

Connect your first repository in about a minute. No credit card. DocPilot writes and maintains the documentation from your code.

Read next