Docs as code: what it means, the tools and a step-by-step workflow

Docs as code means writing documentation in plain text, in Git, with reviews and automated checks. The tools, an eight-step setup, sample configs and trade-offs.

By Strokes Team · · 15 min read

Key takeaways

  • Docs as code means writing documentation with the same tools as software: plain-text markup, Git, issue trackers, code review and automated tests.
  • The payoff is timing: a feature and its documentation change in the same pull request, get reviewed together and ship together.
  • Automated checks catch the mechanical problems, such as broken links, off-style terms and malformed Markdown, so human reviewers can spend their time on accuracy.
  • It suits teams whose writers work alongside developers. It struggles when most contributors never use Git or reviewers expect Word files with tracked changes, and for a single README the pipeline can cost more than it saves.
A laptop on a sunlit desk showing a folder tree, an open document and a branching version graph, between a wooden holder of cards marked with a diagram, code brackets, a gear and a book, and a stack of three blocks, a flask, a gear and a book, each with a green check mark, beside a mug and an open notebook with a fountain pen.
Docs as code puts the documentation on the same line as the product: written, reviewed, checked and shipped together.
Table of contents
  1. One change, start to finish
  2. What docs as code changes
  3. The docs-as-code toolchain
  4. Set up a docs-as-code workflow
  5. When docs as code fits
  6. Common docs-as-code mistakes
  7. Frequently asked questions

Docs as code is a way of writing documentation with the same tools and workflow as software. The docs are plain-text files, usually Markdown, kept in Git next to the code. Changes go through pull requests and review, automated checks test them, and a build publishes them as a website. The Write the Docs community describes it as writing documentation “with the same tools as code” and names five: issue trackers, version control, plain-text markup, code reviews and automated tests.

A developer renames a setting from timeout to timeout_seconds. With docs as code, the reference can change in the same pull request, so the old name doesn't make it into the next release. That change is followed below from branch to published page, then come the tools, an eight-step setup and the cases where the workflow is more than a team needs.

One change, start to finish

The project is acme-cli, an invented command-line tool. Its docs are Markdown files in a docs folder inside the same Git repository as its code. Git is the version control system that records every change to every file, with who made it and a message saying what it was for. Here is how the rename travels:

  1. A branch. The developer works on a branch, a separate line of changes in Git, and edits the code, the configuration reference and the changelog together. She also adds an upgrade page.
  2. A pull request. She opens pull request #482, a request to merge the branch into the main line. It shows every changed line, code and docs side by side, and collects the review comments.
  3. Reviewers by path. A CODEOWNERS file, which maps folders and files to the people responsible for them, asks the CLI maintainers and the docs team for a review without anyone choosing them.
  4. Automated checks. A continuous integration workflow, here GitHub Actions, runs on the pull request. markdownlint checks the Markdown formatting, Vale applies the team's style rules to the prose, lychee checks every link, and MkDocs, the static site generator that turns the docs folder into a website, builds the site in strict mode, which fails on any warning.
  5. Merge and release. With the checks green and both teams' approval, the pull request merges. The release is tagged 2.5, and the published site picks up the new pages.

Here is the pull request description that carried it:

Pull request #482: Rename timeout to timeout_seconds

What changes. The timeout option becomes timeout_seconds in version 2.5. The old name keeps working until 3.0 and logs a deprecation warning.

Code. src/config.py, tests/test_config.py.

Docs. docs/reference/configuration.md (option renamed, new example), docs/how-to/upgrade-to-2.5.md (new page), CHANGELOG.md (Deprecated section).

Checks. markdownlint: passed. Vale: one error, “e-mail” in configuration.md, fixed in the second commit. lychee: one broken link, a typo in the upgrade page's link to the configuration reference, fixed. Build: passed in strict mode.

Review. Approved by the CLI maintainers and the docs team. The docs reviewer asked for the 3.0 deadline in the first paragraph of the upgrade page.

Original example, written for this guide.

Why it works. The rename, the reference page, the upgrade guide and the changelog entry merge as one unit, so there is never a week when the docs describe an option the code no longer has. The checks caught two mechanical problems before anyone read the change, and the human review went to the one thing a machine can't judge: whether a reader upgrading will notice the deadline. The changelog entry uses the Deprecated heading from Keep a Changelog, and the changelog examples show the format. When 2.5 ships, its docs are a tag; the 2.4 docs stay as they were for anyone who hasn't upgraded.

What docs as code changes

In a wiki or a help-center editor, documentation lives apart from the product, and a renamed setting can stay wrong on the page until a reader complains. Docs as code moves the page next to the code it describes, so one pull request can change both, the same reviewers see both and the same release ships both.

Write the Docs lists what teams gain: writers fit in better with development teams, developers often write a first draft, and a team can block the merge of a new feature that arrives without documentation. Three more gains come from Git itself:

  • Every change has a history. Who changed a sentence, when, and in which pull request, with the discussion attached.
  • Docs are versioned with the product. The docs for version 2.4 stay as they were when 2.5 ships, because each release is a tag in the same repository.
  • Machines do the dull checks. Broken links, banned words and malformed tables fail the build instead of reaching readers.

For the approach at book length, the Write the Docs guide recommends Anne Gentle's Docs Like Code.

The docs-as-code toolchain

Each job in the workflow has a few common tools. Most teams pick one per row and keep the set small.

JobCommon toolsWhat it does for the writer
JobWriteCommon toolsMarkdown (CommonMark), reStructuredText, AsciiDocWhat it does for the writerPlain text that any editor opens and Git can compare line by line
JobStore and versionCommon toolsGit, hosted on a platform such as GitHub or GitLabWhat it does for the writerA history of every change, and branches for work in progress
JobReviewCommon toolsPull requests (merge requests on GitLab), a CODEOWNERS fileWhat it does for the writerThe right reviewers are asked automatically, with comments on the exact line
JobCheckCommon toolsmarkdownlint, Vale, lychee, a strict buildWhat it does for the writerFormatting, style and link problems fail before a person reviews the change
JobBuildCommon toolsMkDocs, Sphinx, Docusaurus, Hugo, Jekyll, AntoraWhat it does for the writerTurns a folder of text files into a website with navigation
JobPublishCommon toolsGitHub Pages, Read the DocsWhat it does for the writerPuts the site online when changes merge; Read the Docs also keeps a version per release
One tool per job is plenty. The setup below picks one from each row.

Plain-text markup

John Gruber published Markdown in 2004 so that a document would be “publishable as-is, as plain text”, readable before anything converts it. Its weakness is dialects: tables, footnotes and warning boxes work differently from one tool to the next. CommonMark, “a strongly defined, highly compatible specification of Markdown”, settles the core syntax, and site builders add their own extensions on top. Sphinx reads reStructuredText and, through MyST, Markdown; Antora uses AsciiDoc. Both of those formats have features basic Markdown lacks, such as includes and admonitions.

Git and pull requests

Branches and pull requests work for prose exactly as they do for code. On GitHub, the people a CODEOWNERS file names are asked to review automatically when a pull request touches their files (draft pull requests excepted), and a branch protection rule can require a code owner's approval before the merge. The file can live in .github/, the repository root or docs/; GitHub uses the first one it finds, in that order.

Site builders

A static site generator turns the folder of text files into a website. The common choices differ mostly in markup and ecosystem:

  • MkDocs builds a site from Markdown files and a single YAML configuration file, mkdocs.yml.
  • Sphinx takes reStructuredText or MyST Markdown and outputs HTML, LaTeX for PDF, ePub and more. Python's own documentation is built with it.
  • Docusaurus is built on React, lets writers embed components in Markdown through MDX, and has document versioning built in.
  • Hugo, written in Go, is a general site builder widely used for documentation; the Kubernetes docs are Markdown with custom Hugo shortcodes.
  • Jekyll has built-in support on GitHub Pages.
  • Antora builds AsciiDoc from one or more Git repositories, which suits a product documented across several.

Automated checks

  • markdownlint checks Markdown against numbered rules, such as MD001 (heading levels should go up one at a time) and MD040 (fenced code blocks should name their language).
  • Vale, “a command-line linter for prose”, checks text against style rules you write in YAML or against published styles such as Microsoft's, Google's or Red Hat's. GitLab keeps the Vale rules for its documentation in the repository, under doc/.vale, and enforces the error-level rules in its pipelines.
  • lychee is a link checker written in Rust that finds broken links in Markdown, HTML and other files.
  • A strict build catches the rest: mkdocs build --strict aborts the build on any warning, so a problem nobody would scroll back to read stops the merge.

Set up a docs-as-code workflow

The setup below uses MkDocs, GitHub and GitHub Actions for a small command-line tool called acme-cli. The steps are the same with other tools; only the file names change.

  1. Put the docs next to the code

    Create a docs folder in the product's repository, with one Markdown file per page and subfolders that match the site's sections. Use a separate docs repository only when one set of docs covers several codebases.

    Repository layout

    acme-cli/
    ├── src/
    ├── tests/
    ├── docs/
    │   ├── index.md
    │   ├── get-started/
    │   │   └── install.md
    │   ├── how-to/
    │   │   └── upgrade-to-2.5.md
    │   ├── reference/
    │   │   └── configuration.md
    │   └── concepts/
    │       └── how-caching-works.md
    ├── styles/
    │   └── Acme/
    │       └── Terms.yml
    ├── .github/
    │   ├── CODEOWNERS
    │   └── workflows/
    │       └── docs.yml
    ├── .vale.ini
    ├── mkdocs.yml
    └── CHANGELOG.md
  2. Choose one markup and write down the house rules

    Pick Markdown unless your site builder needs something else, then write a short style guide: terms, headings, code samples, warning boxes. Keep it in the same repository, so a rule change is reviewed like a page change. The style guide examples show what published technical guides cover, and the style guide template gives you the sections.

  3. Configure the site builder

    In MkDocs, one file sets the site name, the navigation and the link back to the repository. With repo_url and edit_uri set, themes that support it show an edit link on every page, which opens that page's source file.

    mkdocs.yml

    site_name: acme-cli documentation
    repo_url: https://github.com/your-org/acme-cli
    edit_uri: edit/main/docs/
    nav:
      - Home: index.md
      - Get started:
          - Install: get-started/install.md
      - How-to guides:
          - Upgrade to 2.5: how-to/upgrade-to-2.5.md
      - Reference:
          - Configuration: reference/configuration.md
      - Concepts:
          - How caching works: concepts/how-caching-works.md

    Run mkdocs serve to preview the site at http://127.0.0.1:8000/ while you write. mkdocs build writes the finished site to a folder named site.

  4. Write on branches, one change per pull request

    Small pull requests get reviewed; a forty-page rewrite on one branch collects merge conflicts. Put the docs change in the same pull request as the code change it describes.

  5. Assign reviewers with CODEOWNERS

    Technical review and docs review are different jobs. The engineer checks that the page is true; the writer checks that a reader can follow it. When several lines match a file, the last one wins, so the configuration page below goes to both teams.

    .github/CODEOWNERS

    # The docs team reviews every change to the docs and the style rules
    /docs/       @your-org/docs-team
    /styles/     @your-org/docs-team
    .vale.ini    @your-org/docs-team
    
    # The configuration page also goes to the engineers who own the code
    /docs/reference/configuration.md  @your-org/cli-maintainers @your-org/docs-team
  6. Automate the checks

    Start Vale with errors only. A first run over an old docs folder can bury a team in warnings, so set MinAlertLevel to error, fix those, then lower the threshold. Your own rules go in a folder under StylesPath; to add a published style, list it under Packages and run vale sync, which Vale's documentation also recommends running in CI before linting. The Vale action in the workflow below runs it for you.

    .vale.ini

    StylesPath = styles
    MinAlertLevel = error
    
    [*.md]
    BasedOnStyles = Vale, Acme

    styles/Acme/Terms.yml

    extends: substitution
    message: "Consider using '%s' instead of '%s'"
    level: error
    ignorecase: true
    swap:
      e-mail: email
      log in to: sign in to
      config file: configuration file

    Then run every check on every pull request. This workflow lints the Markdown, applies the Vale rules, checks the links and builds the site in strict mode; any failure blocks the merge once the check is required.

    .github/workflows/docs.yml

    name: Docs checks
    on: pull_request
    
    jobs:
      docs:
        runs-on: ubuntu-latest
        steps:
          # Action versions current as of November 2026
          - uses: actions/checkout@v7
    
          - name: Lint Markdown
            run: npx --yes markdownlint-cli2 "docs/**/*.md"
    
          - name: Check style with Vale
            uses: vale-cli/vale-action@v3
            with:
              files: docs
              fail_on_error: true
    
          - name: Check links
            uses: lycheeverse/lychee-action@v2
            with:
              args: --no-progress "docs/**/*.md"
              fail: true
    
          - uses: actions/setup-python@v7
            with:
              python-version: "3.12"
    
          - name: Build the site in strict mode
            run: |
              pip install "mkdocs<2"
              mkdocs build --strict
  7. Publish on merge and version with releases

    GitHub Pages can publish when changes are pushed to a branch, from the root or a /docs folder, or from a GitHub Actions workflow, and mkdocs gh-deploy builds the docs and commits them to a gh-pages branch. For versioned docs, Read the Docs, which hosts “any tool that can output HTML”, turns Git tags and branches into versions you activate. Its latest follows the default branch and stable tracks the newest stable release.

  8. Make docs part of done

    Add a docs checkbox to the pull request template and agree which changes need documentation: new settings, changed behavior, new error messages. A feature isn't finished until its page is, and the reviewer who approves the code checks that the box is ticked honestly.

When docs as code fits

It fits when

  • Writers and developers work on the same product and release schedule
  • The docs must match specific versions of the software
  • Some reference text sits in the code itself, such as API descriptions or command help
  • Changes need review and a traceable history
  • Contributors already use Git, or can learn the basics

It struggles when

  • Most contributors are support agents, lawyers or marketers who never touch Git
  • Reviewers expect Word files with tracked changes
  • A large content set depends on heavy reuse and translation workflows built into structured authoring tools
  • Nobody owns the build, so one broken check blocks every fix
  • Writers spend more time on merge conflicts than on sentences

It can also be more than a team needs. A single README, or a small help center that one person updates a few times a year, gets little from a site builder, three linters and a pipeline someone has to keep running. Keep that README in the repository, review its changes in pull requests as you already do, and add the build and the checks when there are enough pages for links and terms to drift.

You don't have to move everything. Reference pages and developer guides can live in the repository while a support team keeps its help center in the tool it already uses, with links between the two. A README is often the first page a team moves, and the product documentation template helps plan which pages follow it into the docs folder.

Common docs-as-code mistakes

  • Moving the files but not the habit. Docs in Git that are still written after the release are a wiki with extra steps. The gain comes from changing the docs in the same pull request as the code.
  • Turning on every check at once. Hundreds of warnings on day one teach people to ignore the checks. Start with errors and raise the bar as the backlog clears.
  • Treating a green build as a review. A linter knows that “e-mail” is off-style. It doesn't know whether step 3 is true. Keep a human technical review and a human edit.
  • Locking yourself into a dialect. Every builder-specific extension, such as tabs or custom shortcodes, is something to convert if you ever change builders. Use them where they earn their place.
  • Long-lived docs branches. A rewrite kept on a branch for six weeks collides with every small fix made in the meantime. Merge it in pieces.
  • Shutting out people who don't use Git. Support, product and legal know things the docs need. Give them the edit link, or a simple way to send changes to a writer.
  • No owner for the pipeline. When the build breaks and nobody owns it, every docs fix waits. Name an owner, as you would for any other service.

The writing itself doesn't change with the tooling. The principles in what technical writing is apply to a Markdown file exactly as they do to a printed manual.

If you draft long pages in Strokes, import the Markdown files from the docs folder into a project and export finished pages back to Markdown before you commit. For a rename like #482, agent mode can update every page in the project that uses the old name, showing each step, before Vale and your reviewers see the pull request.

Frequently asked questions

Is docs as code only for software documentation?

No. The workflow suits any documentation that changes often and benefits from review and history: internal handbooks, policies, style guides, course materials and even books. What it needs is contributors willing to use Git, or a writer who handles Git for them. Content reviewed mainly in Word by people outside the team is usually easier to manage elsewhere.

Do technical writers need to know Git?

For docs as code, yes, at least the basics: cloning a repository, creating a branch, committing, pushing, opening a pull request and responding to review. Resolving a simple merge conflict is the next skill to learn. Writers don't need to program, but being able to read the code a page documents helps them check that the page is right.

What is the difference between docs as code and a wiki?

A wiki stores pages in its own system and usually lets people edit them directly, with no review before a change goes live. Docs as code keeps pages as files in a Git repository next to the product, so every change can go through review and automated checks and be tied to a specific release. A wiki is quicker to edit; docs as code is easier to keep accurate.

Read next