Runbook: Cloudflare Pages New Site Setup

Last Updated

2026-02-17

Owner

Evan Rosado

Frequency

As needed (new site deployment)


Purpose

Deploy a new Antora documentation site to Cloudflare Pages with custom domain and proper security settings.

Two deployment strategies:

Strategy Description Best For

CLI + CI (Recommended)

Create project via netapi, deploy via GitHub Actions/GitLab CI

Multi-forge portability, full automation

Dashboard + Git Integration

Cloudflare pulls from GitHub/GitLab, builds internally

Simple setup, single-forge projects

Prerequisites

Requirement Details Verified

Cloudflare account

Access to dashboard.cloudflare.com

[ ]

GitHub repository

Public or private, contains Antora playbook

[ ]

Domain in Cloudflare

DNS managed by Cloudflare

[ ]

Repository builds locally

make or npx antora succeeds

[ ]

No secrets in repo

Run security audit (see below)

[ ]

Security Audit (Pre-Deployment)

Before deploying any site publicly, verify no sensitive data:

# Set repo path
REPO_PATH="/path/to/your/repo"

# Scan for secrets/tokens
grep -rIE '(password|secret|token|api.?key|private.?key|BEGIN.*(RSA|PRIVATE))' \
  --include="*.adoc" --include="*.yml" --include="*.yaml" --include="*.json" \
  "$REPO_PATH" | grep -v node_modules

# Scan for internal IPs/hostnames
grep -rIE '(inside\.|\.internal|10\.50\.|192\.168\.[0-9]+\.[0-9]+)' \
  --include="*.adoc" "$REPO_PATH"

# Find sensitive files
find "$REPO_PATH" -name ".env*" -o -name "*.pem" -o -name "*.key" -o -name "credentials*" \
  | grep -v node_modules

Expected result: No matches, or only safe references (example IPs, ${{ secrets.* }} patterns).

Full programmatic deployment - zero dashboard interaction.

Prerequisites (CLI)

# Load Cloudflare credentials
dsource d000 dev/app

# Verify token works
netapi cloudflare zones

Step 1: Create Pages Project

netapi cloudflare pages create architectus-docs --output "build/site"

Step 2: Configure GitHub Actions

Create .github/workflows/build.yml:

name: Build Docs

on:
  push:
    branches: [main]
  repository_dispatch:
    types: [spoke-updated]
  workflow_dispatch:

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

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

      - run: npm ci

      - run: npx antora antora-playbook.yml

      - name: Deploy to Cloudflare Pages
        uses: cloudflare/wrangler-action@v3
        with:
          apiToken: $\{{ secrets.CF_API_TOKEN }}
          accountId: $\{{ secrets.CF_ACCOUNT_ID }}
          command: pages deploy build/site --project-name=architectus-docs

Step 3: Set GitHub Secrets

# Use CI-specific token (no IP filter)
gh secret set CF_API_TOKEN --repo EvanusModestus/architectus-docs --body "<ci-pages-deploy-only-token>"
gh secret set CF_ACCOUNT_ID --repo EvanusModestus/architectus-docs --body "<account-id>"

Step 4: Add Custom Domain

netapi cloudflare pages domain architectus-docs docs.architectus.dev

Step 5: Create DNS Record

# If using separate DNS token
CF_API_TOKEN=$CF_DNS_TOKEN netapi cloudflare dns add architectus.dev \
    -t CNAME -n docs -c architectus-docs.pages.dev --proxied

Step 6: Verify

# Trigger deployment
git push origin main

# Watch workflow
gh run watch --repo EvanusModestus/architectus-docs

# Verify site
curl -sI https://docs.architectus.dev | head -5

API Token Strategy

Create separate tokens for security:

Token Permissions Notes

CF_API_TOKEN (local)

Pages:Read, Access:Edit, Cache Purge

IP-filtered to your machine

CF_DNS_TOKEN (local)

Zone:Zone:Read, Zone:DNS:Edit

IP-filtered to your machine

ci-pages-deploy-only (CI)

Account:Cloudflare Pages:Edit

No IP filter (CI runners need access)

Dashboard Workflow (Alternative)

Use this if you prefer GUI or need git-connected builds where Cloudflare runs the build.

Step 1: Create Pages Project (Dashboard)

1.1 Navigate to Pages

  1. Login to dash.cloudflare.com

  2. Select account (if multiple)

  3. Left sidebar → Workers & Pages

  4. Click CreatePagesConnect to Git

1.2 Connect Repository

  1. Select GitHub (or GitLab)

  2. If first time: Authorize Cloudflare to access repos

  3. Select repository (e.g., EvanusModestus/architectus-docs)

  4. Click Begin setup

1.3 Configure Build Settings

Setting Value

Project name

architectus-docs (auto-generated, can modify)

Production branch

main

Framework preset

None (leave blank)

Build command

npm ci && npx antora antora-playbook.yml

Build output directory

build/site

Root directory

/ (leave default unless playbook is nested)

1.4 Environment Variables

Click Environment variables → Add:

Variable Value Purpose

NODE_VERSION

20

Use Node.js 20.x

NPM_FLAGS

--prefer-offline

Faster installs (optional)

For private UI bundles (Cloudflare Access protected):

Variable Value

CF_ACCESS_CLIENT_ID

Service token ID from Cloudflare Access

CF_ACCESS_CLIENT_SECRET

Service token secret

1.5 Deploy

  1. Click Save and Deploy

  2. Wait for build (2-5 minutes for Antora sites)

  3. Verify deployment at <project-name>.pages.dev

Step 2: Custom Domain

2.1 Add Domain

  1. Go to project → Custom domains tab

  2. Click Set up a custom domain

  3. Enter domain: docs.architectus.dev

  4. Click Continue

2.2 DNS Configuration

Cloudflare typically auto-configures if DNS is managed by Cloudflare.

If manual configuration needed:

Type Name Content Proxy

CNAME

docs

architectus-docs.pages.dev

Proxied (orange cloud)

2.3 SSL/TLS

  1. Go to domain → SSL/TLSOverview

  2. Set mode: Full (strict)

  3. Edge Certificates: Enabled (automatic)

Step 3: Access Control (Optional)

For private/internal sites, add Cloudflare Access:

3.1 Create Access Application

  1. Go to Zero TrustAccessApplications

  2. Click Add an applicationSelf-hosted

  3. Configure:

    Setting Value

    Application name

    Architectus Docs

    Session duration

    24 hours

    Application domain

    docs.architectus.dev

3.2 Add Policy

  1. Policy name: Allowed Users

  2. Action: Allow

  3. Include rules:

    • Email ends with: @yourdomain.com

    • Or: Email is: specific@email.com

Step 4: Verify Deployment

4.1 Check Build Logs

  1. Project → Deployments tab

  2. Click latest deployment

  3. Review Build log for errors

4.2 Test Site

# Check site is accessible
curl -sI https://docs.architectus.dev | head -5

# Expected:
# HTTP/2 200
# content-type: text/html

4.3 Test Search (if Lunr enabled)

  1. Navigate to site

  2. Use search box

  3. Verify results appear

Troubleshooting

Build Fails: Module Not Found

Error: Cannot find module '@antora/lunr-extension'

Fix: Ensure package.json includes all dependencies:

npm install @antora/lunr-extension --save
git add package.json package-lock.json
git commit -m "fix: add missing Antora extension"
git push

Build Fails: Node Version

Error: Syntax errors or API incompatibilities

Fix: Set NODE_VERSION=20 in environment variables.

Custom Domain Not Working

Symptoms: DNS_PROBE_FINISHED_NXDOMAIN

Fix: . Verify CNAME record exists in Cloudflare DNS . Wait 5-10 minutes for propagation . Check domain status in Pages → Custom domains

403 Forbidden (Access Protected)

Symptoms: Site returns 403 even for allowed users

Fix: . Check Access policy configuration . Verify user email matches policy rules . Clear browser cookies and retry

Maintenance

Redeploy Manually

# Trigger rebuild via empty commit
cd /path/to/repo
git commit --allow-empty -m "chore: trigger rebuild"
git push origin main

View Deployment History

  1. Project → Deployments tab

  2. Click any deployment to view logs

  3. Rollback button available for previous deployments

Update Build Settings

  1. Project → SettingsBuilds & deployments

  2. Modify build command or environment variables

  3. Changes apply to next deployment

Quick Reference

Standard Antora Build Settings

Setting Value

Build command

npm ci && npx antora antora-playbook.yml

Output directory

build/site

Node version

20

Common Environment Variables

Variable Purpose

NODE_VERSION

Node.js version (use 20)

ANTORA_CACHE_DIR

Custom cache location

CI

Set to true (auto-set by CF)