Skip to content

Cloudflare Pages Deployment - Design Documentation

Workflow: docs-cloudflare.yml Purpose: Build MkDocs documentation and deploy to Cloudflare Pages Status: ✅ Production Ready


Architecture Overview

┌─────────────────────────────────────────────────────────────────┐
│                     TRIGGER CONDITIONS                          │
├─────────────────────────────────────────────────────────────────┤
│ • Push to main (*.md, docs config, workflow file)              │
│ • Manual workflow_dispatch                                      │
│ • Weekly schedule (Sunday 00:00 UTC)                            │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│                   JOB 1: BUILD DOCUMENTATION                    │
├─────────────────────────────────────────────────────────────────┤
│ Runner: ubuntu-latest                                           │
│                                                                 │
│ Steps:                                                          │
│ 1. Configure Git (symlinks=false for Windows compat)           │
│ 2. Checkout repo (full history for git plugins)                │
│ 3. Setup Python 3.11 + pip cache                               │
│ 4. Install MkDocs + plugins                                    │
│ 5. Configure Git user (for git-authors plugin)                 │
│ 6. Build docs → ABSOLUTE PATH ★                                │
│    mkdocs build --site-dir /workspace/var/docs/user            │
│ 7. Verify build output ★                                       │
│    • Check directory exists                                    │
│    • Count HTML files > 0                                      │
│    • Exit 1 if validation fails                                │
│ 8. Generate build summary (GitHub step summary)                │
│ 9. Upload artifact: "documentation"                            │
│    • Path: var/docs/user                                       │
│    • Retention: 1 day                                          │
│    • Fail if no files found                                    │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│                 JOB 2: DEPLOY TO CLOUDFLARE PAGES               │
├─────────────────────────────────────────────────────────────────┤
│ Runner: ubuntu-latest                                           │
│ Depends on: build                                               │
│                                                                 │
│ Steps:                                                          │
│ 1. Download artifact: "documentation" → dist/                   │
│ 2. Deploy to Cloudflare Pages                                  │
│    • Project: audiolab                                         │
│    • Directory: dist/                                          │
│    • Branch: main                                              │
│ 3. Generate deployment summary                                 │
└─────────────────────────────────────────────────────────────────┘
                   https://audiolab.pages.dev

= Critical design elements (see Critical Design Decisions)


Critical Design Decisions

1. Absolute Path for site_dir

Decision: Override MkDocs site_dir config with --site-dir CLI flag using absolute path

Rationale: - Relative paths (../../../../var/docs/user) don't resolve consistently in CI - ${{ github.workspace }} is guaranteed correct by GitHub Actions - Eliminates path ambiguity and silent failures

Implementation:

- name: Build documentation
  run: |
    cd 03_INFRA/03_11_documentation_platform/03_11_01_user_documentation
    mkdocs build --verbose --site-dir "${{ github.workspace }}/var/docs/user"

Alternative Considered: Fix relative path in mkdocs.yml - ❌ Rejected: Would break local development builds - ✅ Current solution: Works in both CI and local environments


2. Explicit Build Output Verification

Decision: Add dedicated verification step after build that FAILS if no HTML files generated

Rationale: - MkDocs with strict: false can succeed without creating files - Prevents silent failures that manifest later in deploy job - Provides clear error messages at correct failure point

Implementation:

- name: Verify build output exists
  run: |
    cd ${{ github.workspace }}

    if [ ! -d "var/docs/user" ]; then
      echo "❌ ERROR: Build directory NOT found"
      exit 1
    fi

    HTML_COUNT=$(find var/docs/user -name "*.html" | wc -l)
    if [ "$HTML_COUNT" -eq 0 ]; then
      echo "❌ ERROR: No HTML files generated!"
      exit 1
    fi

    echo "✅ Build successful: $HTML_COUNT HTML files"

Why This Matters: - Build job fails EARLY if output missing - Deploy job never tries to download non-existent artifact - Error location matches actual problem location


Decision: Configure git config --global core.symlinks false before checkout

Rationale: - Repository contains symlinks (used in local Windows development) - GitHub Actions runners don't need symlinks - Prevents potential checkout issues on different platforms

Implementation:

- name: Configure Git to use regular files instead of symlinks
  run: git config --global core.symlinks false

- name: Checkout repository
  uses: actions/checkout@v4

Impact: Symlinks become regular files in CI (acceptable for documentation build)


4. Full Git History Required

Decision: Checkout with fetch-depth: 0

Rationale: - git-revision-date plugin needs full history for "last updated" dates - git-authors plugin needs history for contributor information - Shallow clone would break these features

Implementation:

- uses: actions/checkout@v4
  with:
    fetch-depth: 0  # Full history needed for git plugins

Trade-off: Slower checkout vs. accurate metadata (worth it)


5. Separate Build/Deploy Jobs

Decision: Two jobs instead of one monolithic job

Rationale: - Separation of concerns: Build vs. Deploy - Artifact as contract: Clean interface between jobs - Reusability: Deploy job could be reused for manual deploys - Debugging: Easier to identify which phase failed

Job Dependencies:

jobs:
  build:
    runs-on: ubuntu-latest
    # ... build steps ...

  deploy:
    runs-on: ubuntu-latest
    needs: build  # Won't run if build fails
    # ... deploy steps ...

Alternative Considered: Single job - ❌ Rejected: Harder to debug, less modular, can't reuse deploy step


MkDocs Configuration Integration

Key Config Settings

# mkdocs.yml
site_dir: ../../../../var/docs/user  # Used for local builds
strict: false                         # Allow warnings (for dev)

plugins:
  - gen-files:      # Generates nav from repo structure
      scripts:
        - scripts/gen_nav.py
  - literate-nav:   # Reads generated SUMMARY.md
  - section-index   # README.md as section index
  - search          # Full-text search

How gen-files Plugin Works

  1. Build starts: MkDocs initializes plugins
  2. gen-files runs: Executes scripts/gen_nav.py
  3. Virtual files created: Script walks repo, creates virtual .md files in memory
  4. Navigation generated: Writes SUMMARY.md with literate-nav syntax
  5. MkDocs processes: Converts virtual files to HTML
  6. Output written: All HTML written to site_dir

Critical Insight: If site_dir doesn't get created, steps 1-5 succeed but step 6 fails silently with strict: false


Workflow Variables

GitHub Context Variables Used

Variable Value Usage
${{ github.workspace }} /home/runner/work/AudioLab/AudioLab Root workspace path
${{ github.sha }} 96038819... Commit SHA for summaries
${{ github.ref_name }} main Branch name for Cloudflare
${{ github.event_name }} push / workflow_dispatch Trigger type
${{ secrets.CLOUDFLARE_API_TOKEN }} *** Cloudflare authentication
${{ secrets.CLOUDFLARE_ACCOUNT_ID }} *** Cloudflare account
${{ secrets.GITHUB_TOKEN }} Auto-generated For git-revision-date plugin

Computed Paths

# Working directory during build
WORK_DIR=/home/runner/work/AudioLab/AudioLab/03_INFRA/03_11_documentation_platform/03_11_01_user_documentation

# Output directory (absolute)
SITE_DIR=/home/runner/work/AudioLab/AudioLab/var/docs/user

# Artifact path (relative to workspace)
ARTIFACT_PATH=var/docs/user

Error Handling Strategy

Build Phase

# Step 1: Build (allows warnings)
mkdocs build --verbose --site-dir "${{ github.workspace }}/var/docs/user"

# Step 2: Verify (strict validation)
if [ ! -d "var/docs/user" ]; then
  echo "❌ ERROR: Build directory NOT found"
  ls -la  # Show what exists
  exit 1  # FAIL FAST
fi

# Step 3: Count files (quality check)
HTML_COUNT=$(find var/docs/user -name "*.html" | wc -l)
if [ "$HTML_COUNT" -eq 0 ]; then
  echo "❌ ERROR: No HTML files generated!"
  exit 1  # FAIL FAST
fi

Deploy Phase

# Step 1: Download artifact
- uses: actions/download-artifact@v4
  with:
    name: documentation
    path: dist

# Step 2: Deploy (fails if API issues)
- uses: cloudflare/pages-action@v1
  with:
    apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
    accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
    projectName: audiolab
    directory: dist

Error Propagation: - Build verification fails → Build job fails → Deploy job skipped - Artifact download fails → Deploy job fails (shouldn't happen if build passed) - Cloudflare deploy fails → Deploy job fails, artifact retained for debugging


Performance Characteristics

Build Job

Phase Duration Cacheable
Git checkout (full history) ~10s ❌ No
Python setup ~5s ✅ Yes (pip cache)
Install dependencies ~15s ✅ Yes (pip cache)
MkDocs build ~30s ❌ No
Verify output ~2s ❌ No
Upload artifact ~5s ❌ No
Total ~67s -

Deploy Job

Phase Duration Cacheable
Download artifact ~3s ❌ No
Cloudflare deploy ~60-90s ❌ No
Total ~63-93s -

Total Workflow Duration: ~130-160 seconds

Optimization Opportunities

  1. Implemented: Python pip cache
  2. Implemented: Parallel jobs (build/deploy are sequential by design)
  3. ⚠️ Possible: Cache MkDocs plugins install
  4. ⚠️ Possible: Incremental builds (only changed files)
  5. Not viable: Cache full git history (defeats purpose of git plugins)

Monitoring & Observability

Success Indicators

  • ✅ Build job completes with exit code 0
  • ✅ HTML file count > 0 in verification step
  • ✅ Artifact uploaded successfully
  • ✅ Deploy job downloads artifact
  • ✅ Cloudflare Pages deployment succeeds
  • ✅ Site accessible at https://audiolab.pages.dev

Failure Detection

# GitHub Step Summary (visible in workflow UI)
echo "## 📚 Documentation Build Summary" >> $GITHUB_STEP_SUMMARY
echo "- **Files generated**: $FILE_COUNT" >> $GITHUB_STEP_SUMMARY

# Error annotation
echo "::error title=Build Failed::No HTML files generated"

# Warning annotation
echo "::warning title=Large Build::Generated $FILE_COUNT files"

# Notice annotation
echo "::notice::Build completed in 30s"

Debugging Information Logged

  1. Build context:

    Current directory: /workspace/...
    Target site_dir: /workspace/var/docs/user
    

  2. Build results:

    Total files: 450
    HTML files: 120
    Sample files: [list of first 10 HTML files]
    

  3. Deployment info:

    Deploying to: audiolab.pages.dev
    Branch: main
    Commit: 96038819
    


Maintenance Procedures

Updating MkDocs Version

# Update requirements.txt
cd 03_INFRA/03_11_documentation_platform/03_11_01_user_documentation
pip install --upgrade mkdocs mkdocs-material
pip freeze > requirements.txt

# Test locally
mkdocs build --site-dir ../../../../var/docs/user

# Commit and push (workflow will test in CI)
git add requirements.txt
git commit -m "chore(docs): update MkDocs to vX.Y.Z"

Changing Site Directory

Option 1: Local only (recommended)

# mkdocs.yml - for local dev
site_dir: ../../../../var/docs/user

# workflow - override in CI
mkdocs build --site-dir "${{ github.workspace }}/new/path"

Option 2: Global change

# Update mkdocs.yml
site_dir: ../../../../new/path

# Update workflow
mkdocs build --site-dir "${{ github.workspace }}/new/path"

# Update artifact upload
path: new/path

Adding New Plugins

# 1. Install locally
cd 03_INFRA/03_11_documentation_platform/03_11_01_user_documentation
pip install mkdocs-plugin-name

# 2. Update requirements.txt
pip freeze > requirements.txt

# 3. Add to mkdocs.yml
# plugins:
#   - plugin-name

# 4. Test build
mkdocs build --site-dir ../../../../var/docs/user

# 5. Commit changes
git add requirements.txt mkdocs.yml
git commit -m "feat(docs): add plugin-name for feature X"

Security Considerations

Secrets Management

Secret Purpose Scope Rotation
CLOUDFLARE_API_TOKEN Deploy to Cloudflare Pages Repo Manual
CLOUDFLARE_ACCOUNT_ID Identify Cloudflare account Repo Static
GITHUB_TOKEN Git metadata for plugins Auto-generated per run Per-run

Permissions

permissions:
  contents: read      # Read repo files
  deployments: write  # Create deployment status

Principle: Minimal permissions required for workflow operation

Content Security

  • ✅ Source: Trusted (repository markdown files)
  • ✅ Build: Deterministic (MkDocs generates static HTML)
  • ✅ Deploy: Isolated (Cloudflare Pages sandbox)
  • ⚠️ External links: User-provided (in markdown)
  • ⚠️ JavaScript: MkDocs theme + plugins only

Rollback Procedures

Rollback via Git

# Find previous successful deployment
git log --oneline | grep "deploy"

# Revert to previous commit
git revert HEAD
git push

# Or force push previous commit (DANGEROUS)
git reset --hard <previous-commit>
git push --force

Rollback via Cloudflare Dashboard

  1. Go to Cloudflare Pages dashboard
  2. Select "audiolab" project
  3. View deployments
  4. Click "Rollback" on previous successful deployment

Emergency: Manual Build & Deploy

# 1. Build locally
cd 03_INFRA/03_11_documentation_platform/03_11_01_user_documentation
mkdocs build --site-dir ../../../../var/docs/user

# 2. Deploy manually
cd ../../../../var/docs/user
npx wrangler pages publish . --project-name=audiolab


Changelog

Date Change Reason
2025-10-17 Use absolute path for site_dir Fix silent build failures
2025-10-17 Add build output verification Fail fast on empty builds
2025-10-17 Fix artifact upload path Use relative path from workspace
2025-10-15 Initial implementation Deploy docs to Cloudflare Pages

Document Status: ✅ Complete Last Updated: 2025-10-17 Maintained By: AudioLab Infrastructure Team