Skip to main content

CI/CD — GitHub Actions Integration

Learning Focus

By the end of this module you will be able to build a complete GitHub Actions CI/CD pipeline that builds your Docusaurus site, runs tests, and deploys to Cloudflare Pages — giving you more control than the native Git integration.

When to Use GitHub Actions vs Native Git Integration

ScenarioRecommendation
Simple static site, no pre-deploy tests✅ Use native Git integration
Need to run tests before deploy✅ Use GitHub Actions
Need to trigger deploy from scripts/CI✅ Use GitHub Actions + Wrangler
Monorepo with conditional deploys✅ Use GitHub Actions
Need deployment status in Slack/Teams✅ Use GitHub Actions
Custom build environment not supported by Pages✅ Use GitHub Actions
Dual Approach

You can disable the native Git integration and use only GitHub Actions, OR keep both (native integration as fallback + GitHub Actions for test-gated deploys). The author's tested approach uses GitHub Actions for control while keeping native integration disabled.


Method 1: Wrangler Action (Official)

Cloudflare provides an official GitHub Action: cloudflare/wrangler-action.

Basic Workflow

.github/workflows/deploy.yml
name: Deploy to Cloudflare Pages

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
deploy:
runs-on: ubuntu-latest
permissions:
contents: read
deployments: write # Required for GitHub deployment status

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

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Build Docusaurus
run: npm run build

- name: Deploy to Cloudflare Pages
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy build --project-name my-docs
gitHubToken: ${{ secrets.GITHUB_TOKEN }}

The gitHubToken field enables GitHub deployment status comments on PRs.

Full Production Workflow (With Tests)

.github/workflows/deploy-with-tests.yml
name: Test & Deploy Docusaurus

on:
push:
branches: [main, staging]
pull_request:
branches: [main]

env:
NODE_VERSION: '20'

jobs:
# ─── Job 1: Lint & Build ──────────────────────────────
build:
name: Build
runs-on: ubuntu-latest
outputs:
# Pass build status to deploy job
build_success: ${{ steps.build.outcome }}

steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Check for broken links (optional)
run: npx docusaurus build --no-minify 2>&1 | grep -i "broken\|error" || true

- name: Build
id: build
run: npm run build

- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: docusaurus-build
path: build/
retention-days: 1

# ─── Job 2: Deploy ────────────────────────────────────
deploy:
name: Deploy
runs-on: ubuntu-latest
needs: build
permissions:
contents: read
deployments: write

steps:
- uses: actions/checkout@v4

- name: Download build artifact
uses: actions/download-artifact@v4
with:
name: docusaurus-build
path: build/

- name: Deploy to Cloudflare Pages
id: deploy
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: >
pages deploy build
--project-name my-docs
--branch ${{ github.ref_name }}
--commit-dirty=true
gitHubToken: ${{ secrets.GITHUB_TOKEN }}

- name: Show deployment URL
run: echo "Deployed to ${{ steps.deploy.outputs.deployment-url }}"

# ─── Job 3: Notify ────────────────────────────────────
notify:
name: Notify
runs-on: ubuntu-latest
needs: [build, deploy]
if: always() && github.ref == 'refs/heads/main'

steps:
- name: Notify on success
if: needs.deploy.result == 'success'
run: |
echo "✅ Deployment to production succeeded"
# Add Slack/Discord webhook here

- name: Notify on failure
if: needs.deploy.result == 'failure'
run: |
echo "❌ Deployment failed! Check GitHub Actions logs."

Method 2: Direct Wrangler CLI in Steps

For more control without the Action:

.github/workflows/manual-deploy.yml
name: Manual Deploy via Wrangler CLI

on:
workflow_dispatch: # Manual trigger only
inputs:
environment:
description: 'Deploy environment'
required: true
default: 'preview'
type: choice
options:
- preview
- production

jobs:
deploy:
runs-on: ubuntu-latest
environment: ${{ inputs.environment }}

steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'

- run: npm ci

- run: npm run build

- name: Install Wrangler
run: npm install -g wrangler

- name: Deploy (Preview)
if: inputs.environment == 'preview'
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: |
wrangler pages deploy build \
--project-name my-docs \
--branch preview-manual

- name: Deploy (Production)
if: inputs.environment == 'production'
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: |
wrangler pages deploy build \
--project-name my-docs \
--branch main

Setting Up GitHub Secrets

Add these secrets to your GitHub repository:

  1. Go to SettingsSecrets and variablesActions
  2. Click New repository secret
Secret NameValueHow to Get
CLOUDFLARE_API_TOKENYour CF API tokendash.cloudflare.com/profile/api-tokens
CLOUDFLARE_ACCOUNT_IDYour CF Account IDwrangler whoami or CF dashboard sidebar

API Token Permissions Required:

  • Account > Cloudflare Pages > Edit

Disabling Native Git Integration

When using GitHub Actions for deployments, disable the native integration to avoid double builds:

  1. Go to your Pages project → SettingsBuilds & Deployments
  2. Click Pause deployments
  3. Now only your GitHub Actions pipeline triggers builds

Or keep native integration active — it will trigger builds from GitHub pushes, while your Actions workflow also deploys (both are valid, just costs 2 build slots per push).


Caching Dependencies

Speed up CI builds by caching node_modules:

Optimized caching setup
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm' # Automatically caches ~/.npm based on package-lock.json

# This reduces npm install from ~60s to ~5s on cache hit

For Yarn or pnpm:

- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'yarn' # or 'pnpm'

Deployment Status Outputs

The cloudflare/wrangler-action provides these outputs:

- name: Deploy
id: deploy
uses: cloudflare/wrangler-action@v3
with:
# ...

- name: Use deployment outputs
run: |
echo "URL: ${{ steps.deploy.outputs.deployment-url }}"
echo "ID: ${{ steps.deploy.outputs.deployment-id }}"
echo "Alias: ${{ steps.deploy.outputs.pages-deployment-alias-url }}"

Scheduled Builds (Cron)

Trigger builds on a schedule — useful for:

  • Rebuilding docs that pull from external APIs
  • Refreshing timestamps
.github/workflows/scheduled-rebuild.yml
name: Scheduled Rebuild

on:
schedule:
- cron: '0 6 * * *' # Daily at 6:00 AM UTC
workflow_dispatch: # Also allow manual trigger

jobs:
rebuild:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'

- run: npm ci
- run: npm run build

- uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy build --project-name my-docs

Multi-Environment Deployment Matrix

.github/workflows/multi-env.yml
name: Multi-Environment Deploy

on:
push:
branches: [main, staging, develop]

jobs:
deploy:
runs-on: ubuntu-latest
permissions:
contents: read
deployments: write

strategy:
matrix:
include:
- branch: main
environment: production
pages_branch: main
- branch: staging
environment: staging
pages_branch: staging
- branch: develop
environment: develop
pages_branch: develop

# Only run for the branch that was pushed
if: github.ref_name == matrix.branch

steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npm run build
env:
NODE_ENV: ${{ matrix.environment }}

- uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy build --project-name my-docs --branch ${{ matrix.pages_branch }}
gitHubToken: ${{ secrets.GITHUB_TOKEN }}

Key Takeaways

  • Use the official cloudflare/wrangler-action@v3 for the simplest GitHub Actions integration.
  • Store CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID as GitHub repository secrets.
  • Use artifact upload/download between jobs to avoid rebuilding.
  • Add gitHubToken: ${{ secrets.GITHUB_TOKEN }} to get PR deployment status comments.
  • Disable native Pages Git integration if you want to control builds exclusively through GitHub Actions.
  • Use workflow_dispatch for manual production deployments with human approval.

What's Next