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¶
- In the Cloudflare dashboard go to Workers & Pages -> Create -> Pages.
- Choose Upload assets (a.k.a. "Direct Upload") — we deploy from GitHub Actions, so you do NOT need to connect Cloudflare to the repo.
- Name the project
owl-cicd-docsand create it. - If you pick a different name, update
CF_PAGES_PROJECTin.github/workflows/docs.yml. - 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:
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)¶
- 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).
- Go to Access -> Applications -> Add an application -> Self-hosted.
- Application domain:
owl-cicd-docs.pages.dev(or your custom domain). - Add a policy:
- Action: Allow
- Rule: Emails ending in
@orangewood.co(or use Emails and list specific people). - 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 |