Skip to content

Hosting the docs on Cloudflare Pages (private)

This repo is private, so GitHub Pages is not available on our plan. Instead we publish the documentation site to Cloudflare Pages (free) and lock it down with Cloudflare Access so only our team can read it. The repository stays private the whole time.

How it works: the docs GitHub Actions workflow builds the MkDocs site and uploads the finished HTML to Cloudflare Pages. Nothing about the source code leaves GitHub.

flowchart LR
    push[Push to main] --> gha["GitHub Actions: mkdocs build"]
    gha --> up["wrangler pages deploy site/"]
    up --> cf[Cloudflare Pages]
    cf --> access[Cloudflare Access login]
    access --> team[Team members only]

Do the following one time. It takes ~15 minutes and needs no coding.

Step 1 — Create a Cloudflare account

Sign up at https://dash.cloudflare.com/sign-up (free). If Orangewood already has a Cloudflare account, ask to be added to it instead of making a new one.

Step 2 — Create the Pages project

  1. In the Cloudflare dashboard go to Workers & Pages -> Create -> Pages.
  2. Choose Upload assets (a.k.a. "Direct Upload") — we deploy from GitHub Actions, so you do NOT need to connect Cloudflare to the repo.
  3. Name the project owl-cicd-docs and create it.
  4. If you pick a different name, update CF_PAGES_PROJECT in .github/workflows/docs.yml.
  5. You can upload the placeholder it offers, or just create the empty project; the first real deploy comes from CI.

Step 3 — Get your Account ID and an API token

Account ID: on the Workers & Pages overview page, copy the Account ID shown in the right-hand sidebar.

API token: 1. Go to My Profile -> API Tokens -> Create Token. 2. Use the "Edit Cloudflare Workers" template, or Create Custom Token with permission Account -> Cloudflare Pages -> Edit. 3. Restrict it to your account, create it, and copy the value (shown once).

Step 4 — Add the token and account id as GitHub secrets

In the repo: Settings -> Secrets and variables -> Actions -> New repository secret. Add both:

Secret name Value
CLOUDFLARE_API_TOKEN the API token from Step 3
CLOUDFLARE_ACCOUNT_ID the Account ID from Step 3

Secrets are encrypted and never shown in logs. Until they exist the workflow still runs but skips the deploy step, so nothing breaks in the meantime.

Step 5 — Trigger the first deploy

Either push any change under docs/ci-cd/, or run it manually: repo Actions tab -> docs workflow -> Run workflow. When it finishes, the site is live at:

https://owl-cicd-docs.pages.dev

At this point the site is public-with-an-obscure-URL. Step 6 locks it down.

Step 6 — Lock it down with Cloudflare Access (the important part)

  1. In the dashboard open Zero Trust (a.k.a. Cloudflare One). The first time it asks you to pick a team name and the Free plan (up to 50 users).
  2. Go to Access -> Applications -> Add an application -> Self-hosted.
  3. Application domain: owl-cicd-docs.pages.dev (or your custom domain).
  4. Add a policy:
  5. Action: Allow
  6. Rule: Emails ending in @orangewood.co (or use Emails and list specific people).
  7. Choose a login method (Google, GitHub, or one-time email PIN) and save.

Now visiting the site redirects to a login screen and only approved people get in. New maintainers just need an allowed email — no GitHub repo access required to read the docs.

Optional — custom domain

In the Pages project: Custom domains -> Set up a domain, e.g. docs.orangewood.co. Point the DNS as instructed, then update the Access application domain in Step 6 to match.

Maintenance

Nothing ongoing. Every push to main that touches the docs rebuilds and redeploys automatically. To rotate the token, create a new one in Cloudflare and update the CLOUDFLARE_API_TOKEN secret.

Troubleshooting

Symptom Fix
Deploy step skipped in CI Secrets missing — add CLOUDFLARE_API_TOKEN / CLOUDFLARE_ACCOUNT_ID (Step 4)
project not found The project name in Cloudflare must match CF_PAGES_PROJECT in the workflow (Step 2)
Authentication error Token lacks the Cloudflare Pages: Edit permission, or was mistyped
Site loads without asking for login Access application/policy not set — redo Step 6, confirm the domain matches