Setting Up Docs CI in GitHub Actions: A Complete Guide
A step-by-step guide to running automated documentation tests in GitHub Actions. Covers basic setup, advanced monorepo workflows, PR comment integration, and scheduled drift detection.
Prerequisites: A DocsCI account (free at snippetci.com/signup), a GitHub repository with documentation, and 10 minutes.
Step 1: Add your API token
Generate a DocsCI token from Settings → Tokens. Add it to your GitHub repository under Settings → Secrets and variables → Actions as DOCSCI_TOKEN.
Step 2: The basic workflow (5 minutes)
Create .github/workflows/docsci.yml:
name: DocsCI
on:
push:
branches: [main, master]
pull_request:
paths:
- 'docs/**'
- '*.md'
- '**/*.mdx'
jobs:
docs-ci:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- name: Archive docs
run: tar czf docs.tar.gz docs/ *.md 2>/dev/null || tar czf docs.tar.gz docs/
- name: Run DocsCI
id: docsci
run: |
RESULT=$(curl -sf -X POST https://snippetci.com/api/runs/queue \
-H "Authorization: Bearer ${{ secrets.DOCSCI_TOKEN }}" \
-F "docs_archive=@docs.tar.gz")
echo "$RESULT" | jq .
echo "$RESULT" | jq -e '.status == "passed"'That's it. On every pull request that touches docs, DocsCI runs all code examples and reports back. The step fails if any example is broken, blocking the merge.
Step 3: Add PR comments with inline findings
The basic workflow only fails CI. This version posts an inline comment on the PR with exact file, line number, error, and suggested fix:
- name: Run DocsCI
id: docsci
run: |
RESULT=$(curl -sf -X POST https://snippetci.com/api/runs/queue \
-H "Authorization: Bearer ${{ secrets.DOCSCI_TOKEN }}" \
-F "docs_archive=@docs.tar.gz" \
-F "pr_number=${{ github.event.number }}" \
-F "repo=${{ github.repository }}" \
-F "commit_sha=${{ github.sha }}")
echo "result=$RESULT" >> $GITHUB_OUTPUT
echo "$RESULT" | jq -e '.status == "passed"'
- name: Post PR summary
if: always()
uses: actions/github-script@v7
with:
script: |
const result = JSON.parse('${{ steps.docsci.outputs.result }}' || '{}');
const status = result.status || 'unknown';
const icon = status === 'passed' ? '✅' : '❌';
github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: `${icon} DocsCI: **${status}** — ${result.finding_count ?? 0} finding(s). [View report](https://snippetci.com/runs/${result.run_id})`,
});Step 4: Monorepo support
For monorepos, detect changed packages and run DocsCI only for those packages in parallel:
jobs:
detect-changes:
runs-on: ubuntu-latest
outputs:
packages: ${{ steps.detect.outputs.packages }}
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 2 }
- id: detect
run: |
PKGS=$(git diff --name-only HEAD~1..HEAD \
| grep -E '^packages/[^/]+/' \
| awk -F/ '{print $2}' | sort -u | jq -Rcn '[inputs]')
echo "packages=$PKGS" >> $GITHUB_OUTPUT
docs-ci:
needs: detect-changes
if: ${{ needs.detect-changes.outputs.packages != '[]' }}
strategy:
matrix:
package: ${{ fromJson(needs.detect-changes.outputs.packages) }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run DocsCI for ${{ matrix.package }}
run: |
tar czf /tmp/docs.tar.gz packages/${{ matrix.package }}/docs/
curl -sf -X POST https://snippetci.com/api/runs/queue \
-H "Authorization: Bearer ${{ secrets.DOCSCI_TOKEN }}" \
-F "docs_archive=@/tmp/docs.tar.gz" \
-F "project=${{ matrix.package }}" \
| jq -e '.status == "passed"'Step 5: Nightly drift detection
Your API can drift even when nobody touches the docs. Schedule a nightly check that runs against your staging environment:
name: Nightly Drift Check
on:
schedule:
- cron: '0 2 * * *' # 2 AM UTC
workflow_dispatch:
jobs:
nightly:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: |
find . -name '*.md' -o -name '*.mdx' | tar czf docs.tar.gz -T -
curl -sf -X POST https://snippetci.com/api/runs/queue \
-H "Authorization: Bearer ${{ secrets.DOCSCI_TOKEN }}" \
-F "docs_archive=@docs.tar.gz" \
-F "openapi_url=https://staging-api.example.com/openapi.json" \
-F "full_suite=true" \
| jq -e '.status == "passed"'What to expect
On a typical API docs repo with 50-200 Markdown files, DocsCI takes 30-90 seconds to run. The first run will usually find 5-20 broken examples that have been silently wrong for months. That's normal — the goal is to get to zero broken examples and keep them there.
Full YAML templates available
Copy-paste ready templates in the DocsCI docs.