·10 min readGitHub ActionsCI/CDtutorial

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.