Markdown for Technical Writers: The Docs-as-Code Workflow Guide
“Docs-as-code” is the practice of treating documentation the same way software teams treat code: write in plain text (usually Markdown), version-control in Git, review through pull requests, and build automatically with CI/CD.
The approach has moved from niche to mainstream, and for good reason — it gives technical writers the collaboration and review tools that developers have used for decades. This guide covers the practical setup: which tool to pick, how to structure the review workflow, linting, CI/CD, and the real trade-offs you’ll face.
What Docs-as-Code Looks Like in Practice
In a docs-as-code workflow:
- Source files are Markdown — stored in a Git repository alongside (or separate from) the code they document
- Changes go through pull requests — reviewers comment on line-level diffs, just like code review
- A static site generator builds the site — MkDocs, Docusaurus, Hugo, or Sphinx turns Markdown into a deployed documentation site
- CI/CD builds and deploys automatically — merge to main, and the docs site updates within minutes
This is the approach used by major open-source projects: the React docs, Kubernetes docs, and many commercial API documentation sites all use variants of this workflow.
Choosing a Static Site Generator
This is the most consequential decision. The right tool depends on your audience, tech stack, and how much configuration overhead you can absorb.
MkDocs + Material theme
Best for: Python projects, internal engineering docs, teams who want to be up in an hour
MkDocs is the most approachable option. Install it with pip, write a mkdocs.yml, and run mkdocs serve for a live preview. The Material for MkDocs theme adds search, versioning, admonitions, and a professional design out of the box.
# mkdocs.yml
site_name: My Docs
theme:
name: material
features:
- navigation.tabs
- navigation.sections
- search.suggest
nav:
- Home: index.md
- Getting Started: getting-started.md
- API Reference: api-reference.md
MkDocs supports extended Markdown: admonitions (!!! note), code block line numbers, content tabs, and more. See our MkDocs vs Docusaurus vs GitBook comparison for a full breakdown.
Docusaurus
Best for: JavaScript/React projects, developer-facing docs, versioned API references
Docusaurus (by Meta) is the most feature-rich option for developer documentation. It supports versioned documentation, MDX (Markdown with embedded React components), and integrates well with Algolia search. The downside: it requires Node.js and has more configuration overhead.
npx create-docusaurus@latest my-docs classic
cd my-docs
npm start
Hugo
Best for: Large sites (thousands of pages), teams who need extreme build speed
Hugo is the fastest static site generator available — it can build thousands of pages in seconds. The trade-off is a steeper learning curve. Its template system takes time to master, and its Markdown handling has some quirks (it uses Goldmark by default, which differs slightly from CommonMark).
Sphinx
Best for: Python packages, teams that need API auto-documentation from docstrings
Sphinx uses reStructuredText by default, but the myst-parser extension gives you Markdown input with full Sphinx features. It’s the standard for Python library documentation (NumPy, Django, Flask) because its autodoc system reads Python docstrings and generates API reference pages automatically.
The Review Workflow
The core of docs-as-code is Git-based review. Here’s what a typical workflow looks like.
Branching strategy
Keep it simple. Most documentation teams do well with a single main branch as the source of truth, with short-lived feature branches:
main ← deployed to production
├── docs/update-auth-section
├── docs/add-webhook-guide
└── docs/fix-typos-api-ref
Some teams mirror software release branches (v2.1, v2.2) for versioned documentation.
Pull request review
When a writer opens a PR, reviewers see a line-level diff. They can comment on specific sentences rather than leaving vague feedback on a shared document. This is a significant productivity gain over wiki-based review.
Tips for effective docs PRs:
- Keep PRs focused — one topic or section per PR, not “update all the docs”
- Use the PR description to explain the why (“users were confused about X, so I clarified Y”)
- Request review from subject matter experts for accuracy; editors for style
- Require at least one approval before merge — documentation errors in production are real bugs
Reviewing large diffs for non-git users
For reviewers who aren’t comfortable with git diffs, our Markdown Diff tool lets them paste the before and after versions of a section and see a colour-coded comparison — no git access required.
Linting: markdownlint and Vale
Linting documentation catches problems before review: broken syntax, inconsistent formatting, and prose style issues.
markdownlint
markdownlint enforces Markdown syntax consistency. Run it in CI to catch:
- Missing blank lines around headings
- Hard tabs instead of spaces
- Duplicate headings on the same page
- Inconsistent list marker styles (
-vs*) - Lines over a configured maximum length
npm install -g markdownlint-cli
markdownlint '**/*.md' --ignore node_modules
Configure it with a .markdownlint.json file in the repository root:
{
"default": true,
"line-length": { "line_length": 120 },
"no-duplicate-heading": false
}
Vale: prose style linting
Vale is a linter for prose style. It enforces:
- A style guide (Microsoft, Google, or a custom one you define)
- Banned words and phrases (“simply”, “just”, “easy”, “obviously”)
- Consistent terminology (always “API key”, never “api key” or “apikey”)
- Passive voice frequency limits
# .vale.ini
StylesPath = styles
MinAlertLevel = suggestion
[*.md]
BasedOnStyles = Vale, Microsoft
Vale catches what a human style reviewer would catch — at scale, in seconds. It’s what separates mature docs-as-code setups from basic ones.
CI/CD: Building and Deploying Docs
The CI pipeline for documentation is usually simpler than for software. A typical GitHub Actions workflow for a MkDocs site:
# .github/workflows/docs.yml
name: Docs CI
on:
push:
branches: [main]
pull_request:
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npx markdownlint '**/*.md' --ignore node_modules
build:
runs-on: ubuntu-latest
needs: lint
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- run: pip install mkdocs-material
- run: mkdocs build --strict
deploy:
if: github.ref == 'refs/heads/main'
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- run: pip install mkdocs-material
- run: mkdocs gh-deploy --force
The --strict flag on mkdocs build fails the build if there are any warnings — broken internal links, missing pages — which catches link rot before it ships to production.
Structuring Markdown for Documentation
Well-structured Markdown makes navigation, search, and updates easier.
One topic per file
Each file should cover one complete, focused topic. When a file grows beyond 600–800 lines, it’s usually a sign it should be split into separate pages.
Consistent frontmatter
Define a standard frontmatter schema and enforce it across the team:
---
title: Authentication Guide
description: How to authenticate API requests using API keys or OAuth 2.0.
last_updated: 2026-05-15
---
Heading discipline
- One
# H1per file (the page title) - Use
##and###for sections and subsections - Don’t skip heading levels (don’t jump from
##to####) - Write headings that make sense out of context — search engines and screen readers encounter them in isolation
For more on heading-based navigation and anchor links, see our guide to Markdown internal links.
Callouts and admonitions
Most documentation generators support callout blocks. Use them to highlight warnings, tips, and important notes. In MkDocs Material:
!!! warning "Breaking change"
This endpoint was deprecated in v2.0 and will be removed in v3.0.
Migrate to `/v2/auth` before upgrading.
In Docusaurus (MDX):
:::warning Breaking change
This endpoint was deprecated in v2.0...
:::
See our Markdown admonitions guide for platform-specific admonition syntax.
Code blocks: always specify the language
Always add a language identifier to fenced code blocks. It improves syntax highlighting, helps readers orient quickly, and some generators use the language tag for copy-button behavior.
```bash
curl -X POST https://api.example.com/v1/tokens \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json"
```
For more on code block highlighting across platforms, see our syntax highlighting guide.
The Trade-offs: Docs-as-Code Isn’t for Everyone
Docs-as-code has real costs. Be honest with yourself about them.
Learning curve for non-developers — technical writers who haven’t used Git may need weeks to become comfortable with branching, rebasing, and conflict resolution. Budget for training.
Less intuitive for SME contributions — subject matter experts who write occasional documentation may struggle with the Git workflow. Solutions: a web-based editor (Netlify CMS, TinaCMS) for non-technical contributors, or a contribution guide with screenshots.
No rich inline collaboration — Google Docs-style simultaneous editing doesn’t exist in Git-based workflows. PRs cover review, but live co-writing requires a workaround.
Image management is awkward — images live in the repo or an external CDN. Large images bloat repository size; external images can go dead. Establish an image convention early.
Docs-as-code is clearly the right choice when:
- Documentation lives alongside code and should move with it through releases
- The team is primarily developers already working in Git daily
- You need versioned docs to match versioned software releases
- You want to enforce a style guide at scale with Vale and markdownlint
It might not be the right choice when:
- Most contributors are non-technical
- You need real-time collaborative editing
- The documentation is heavily visual (screenshots, interactive diagrams)
- You don’t have bandwidth to maintain the toolchain
Getting Started: a Minimal Setup
If you want to try docs-as-code without committing to a full setup:
- Create a GitHub repository for your documentation
- Install MkDocs Material:
pip install mkdocs-material - Create
mkdocs.ymlwith the minimal config shown above - Write a
docs/index.mdas your home page - Run
mkdocs serveto preview locally - Enable GitHub Pages and run
mkdocs gh-deployto publish
You’ll have a live documentation site in under an hour. From there, add markdownlint to CI, then Vale when you’re ready to enforce style.
Related Reading
- MkDocs vs Docusaurus vs GitBook: which should you use? — full decision guide with setup examples
- Markdown for GitHub: the complete workflow guide — GitHub-specific Markdown features, PR templates, wikis
- Markdown in Confluence: what works and what doesn’t — for teams who need docs in both systems
- Markdown admonitions and callouts guide
- Markdown syntax highlighting guide