Dev Docs Access: Separate CF Pages Site + Cloudflare Access
Decision
Replace the single mixed Docusaurus site with two separate Cloudflare Pages deployments:
| Site | URL | Audience | Protection |
|---|---|---|---|
| Public docs | docs.rat.gd | End users | None |
| Dev docs | dev-docs.rat.gd | Contributors | Cloudflare Access (Zero Trust) |
Auth is enforced at the network layer by Cloudflare — before any content is served. No login page, no cookies, no code to maintain.
Why not shared-password middleware
- Shared password has no audit trail, no per-user revocation, and can be leaked
/dev/*content in the same deployment as public docs creates accidental-exposure risk- Requires a login page and edge middleware that Cloudflare Access already provides for free
Architecture overview
GitHub org "GDPR-Labs"
└── org members = authorized users
Cloudflare Zero Trust
└── Identity provider: GitHub OAuth
└── Access Application: dev-docs.rat.gd
└── Policy: GitHub org "GDPR-Labs" → Allow
Cloudflare Pages (new project)
└── Repo: same repo, root = website/, build = npm run build:dev
└── Domain: dev-docs.rat.gd
└── Protected by the Access Application above
Same repo
├── docs/dev/ ← content (unchanged)
└── website/ ← ONE Docusaurus install, TWO configs:
├── docusaurus.config.ts → public site (docs.rat.gd)
└── docusaurus.dev.config.ts → dev site (dev-docs.rat.gd)
A single node_modules serves both sites (no duplicated Docusaurus install). The dev site is built with docusaurus build --config docusaurus.dev.config.ts --out-dir build-dev.
Phase 1 — Create the GitHub organization
Why first: the Cloudflare Access policy references the GitHub org name. Define it before configuring CF.
- Go to github.com/organizations/new
- Choose the Free plan
- Set organization name to
GDPR-Labs— this is the slug as created; use this exact capitalization wherever the org name is referenced (GitHub slugs are case-insensitive, but Cloudflare Access matches the org name as GitHub reports it). Note theorganizationNamefield inwebsite/docusaurus.config.tsisgdprlabs— that field is GitHub Pages deploy metadata only and does not need to match. - Skip the "add members" step for now — you'll invite them after setup
- Once created, go to github.com/GDPR-Labs → People → Invite member
- Invite each current repo collaborator by their GitHub username
- They will receive an email — they must accept the invitation before they can log in to the dev docs
Going forward: granting/revoking dev docs access = adding/removing org members. No separate list.
Phase 2 — Register a GitHub OAuth App
Cloudflare Access uses a GitHub OAuth App to authenticate users against GitHub.
- Go to github.com/settings/developers → OAuth Apps → New OAuth App
- Application name:
ROPA 2.0 Dev Docs Access - Homepage URL:
https://dev-docs.rat.gd - Authorization callback URL: GitHub requires a value here. If you already know your Cloudflare Zero Trust team name, enter
https://<your-team>.cloudflareaccess.com/cdn-cgi/access/callback; otherwise enterhttps://dev-docs.rat.gdas a placeholder — Phase 3 step 5 replaces it with the real callback URL
- Application name:
- Click Register application
- On the next page, note the Client ID
- Click Generate a new client secret and note the Client Secret (shown only once — copy it immediately)
Phase 3 — Configure Cloudflare Zero Trust
3a. Add GitHub as identity provider
- Go to one.dash.cloudflare.com → select your account → Zero Trust
- Settings → Authentication → Login methods → Add new → GitHub
- Paste the Client ID and Client Secret from Phase 2
- Copy the Callback URL shown by Cloudflare (format:
https://<your-team>.cloudflareaccess.com/cdn-cgi/access/callback) - Go back to the GitHub OAuth App (github.com/settings/developers → your app) and paste that URL into Authorization callback URL → Update application
- Back in CF Zero Trust, click Save
- Click Test — a GitHub auth popup should open, you should see a success result
3b. Create the Access Application
- In Zero Trust → Access → Applications → Add an application → Self-hosted
- Fill in:
- Application name:
ROPA 2.0 Dev Docs - Session duration:
24 hours(adjust to taste) - Application domain:
dev-docs.rat.gd
- Application name:
- Click Next
- Under Policies → Add a policy:
- Policy name:
GitHub org members - Action: Allow
- Include rule: Selector =
GitHub Organizations, Value =GDPR-Labs(exact capitalization — Cloudflare Access matches the org name as GitHub reports it; verify with a test login)
- Policy name:
- Click Next → Add application
The application is now created. The domain dev-docs.rat.gd doesn't need to exist yet — CF Access will start protecting it as soon as DNS resolves there.
Phase 4 — Add a dev site config to website/ ✅ DONE
Approach changed from the original plan: instead of a separate website-dev/ project (which would duplicate the entire Docusaurus node_modules, ~500 MB), the dev site is a second config file inside website/, sharing the existing install, CSS, and static assets. The Docusaurus CLI selects the config with --config.
4a. Files added/changed
website/docusaurus.dev.config.ts (new) — standalone config for the dev site:
title: "ROPA 2.0 — Dev Docs",url: "https://dev-docs.rat.gd"- docs preset:
path: "../docs/dev",routeBasePath: "/",sidebarPath: "./sidebars-dev.ts"(reuses the existing sidebar file — do not delete it in Phase 6) blog: false,pages: false—pages: falseis required: without it the public homepage (src/pages/index.tsx) is built into the dev site and its/docs/*links break the build- no
custom-DevDocsIconnavbar item
website/package.json — three new scripts:
"start:dev": "docusaurus start --config docusaurus.dev.config.ts --port 3001",
"build:dev": "docusaurus build --config docusaurus.dev.config.ts --out-dir build-dev",
"serve:dev": "docusaurus serve --config docusaurus.dev.config.ts --dir build-dev --port 3001"
docs/dev/architecture.md — added slug: / to the frontmatter so the dev site has a root page (with pages: false there is otherwise nothing at /). On the public site this moves the page from /dev/architecture to /dev/; the two links pointing there (src/pages/index.tsx hero CTA and DevDocsNavbarItem.tsx) were updated to /dev/.
docs/dev/fileFormats.md — the link to the user doc org-import-export is now the absolute URL https://docs.rat.gd/docs/org-import-export (the user docs don't exist on the dev site, so a root-relative link breaks the dev build).
.gitignore — added /website/build-dev/.
4b. Build and test locally
cd website
npm run build:dev # outputs to website/build-dev/, onBrokenLinks: throw
npm run start:dev # dev server on http://localhost:3001
Both npm run build (public) and npm run build:dev (dev) verified passing.
Note: CF Access does not run locally. The protection only applies on the deployed domain. Note: don't run
start/start:devsimultaneously — both configs share the same.docusaurusworking directory.
Phase 5 — Create the Cloudflare Pages project
- Go to dash.cloudflare.com → Workers & Pages → Create → Pages → Connect to Git
- Authorize Cloudflare to access the repo, select it
- Configure the build:
- Project name:
ropa2-dev-docs - Production branch:
main - Root directory:
website - Build command:
npm run build:dev - Build output directory:
build-dev
- Project name:
- Click Save and Deploy — wait for the first build to complete
- In the project → Settings → Builds & deployments → Build watch paths, set Include paths to
website/*anddocs/dev/*so pushes that touch neither skip the build - On the existing public docs project, set Include paths to
website/*anddocs/user/*(note: content lives indocs/, outsidewebsite/, so watchingwebsite/*alone misses content edits; keepdocs/*until Phase 6 removes/dev/*from the public site)
5a. Add the custom domain
- In the Pages project → Custom domains → Set up a custom domain
- Enter
dev-docs.rat.gd - Cloudflare will add the DNS record automatically (since the domain is already on Cloudflare)
- Wait for the domain to become active (usually under 2 minutes)
5b. Attach the Access Application to the Pages project
Cloudflare Pages has a direct integration with Access:
- In the Pages project → Settings → Access policies
- Click Manage (this links to the Access Application created in Phase 3b)
- Confirm
ROPA 2.0 Dev Docsis listed and enabled
Alternatively, the Access Application domain (
dev-docs.rat.gd) already covers the Pages deployment — no extra wiring is needed if the domain matches exactly.
Phase 6 — Clean up the public docs site
Remove dev docs from the existing website/ project so they don't appear publicly.
6a. Update website/docusaurus.config.ts
Remove the dev docs plugin block and the custom-DevDocsIcon navbar item:
// Remove this entire plugin entry:
[
"@docusaurus/plugin-content-docs",
{
id: "dev",
path: "../docs/dev",
routeBasePath: "dev",
sidebarPath: "./sidebars-dev.ts",
},
],
// Remove or update this navbar item:
{
type: "custom-DevDocsIcon",
position: "right",
},
Replace the navbar item with a plain external link to the new site:
{
href: "https://dev-docs.rat.gd",
label: "Dev Docs",
position: "right",
},
6b. Delete now-unused files from website/
rm website/src/components/DevDocsNavbarItem.tsx
rm website/src/theme/NavbarItem/ComponentTypes.tsx
⚠️ Do NOT delete
website/sidebars-dev.ts— since the Phase 4 approach change it is the sidebar of the dev site (docusaurus.dev.config.tsreferences it).
DevDocsNavbarItem (registered as custom-DevDocsIcon via the ComponentTypes.tsx swizzle) only links to /dev/, so both files become dead code once the navbar item is replaced. Its lucide-react dependency can be dropped from website/package.json at the same time unless other components have started using it. Also update the "Developer docs" hero CTA in website/src/pages/index.tsx (currently /dev/) to https://dev-docs.rat.gd.
6c. Verify the public site still builds
cd website && npm run build
No broken links, no references to /dev/*.
Verification checklist
Access control
- Visit
https://dev-docs.rat.gdin an incognito window → Cloudflare Access login screen appears (not the docs) - Log in with a GitHub account that is not in the
GDPR-Labsorg → access denied - Log in with a GitHub account that is in the
GDPR-Labsorg → dev docs load normally - Log out of CF Access (visit
https://dev-docs.rat.gd/cdn-cgi/access/logout) → redirected to login again
Content
- All sidebar sections render correctly on
dev-docs.rat.gd -
https://docs.rat.gd/dev/*returns 404 (dev docs removed from public site) -
https://docs.rat.gd/docs/*still works normally - The "Dev Docs" navbar link on the public site points to
dev-docs.rat.gd
Access management
- Remove a test user from the
GDPR-Labsorg → their existing CF Access session expires within the configured session duration; new login attempt is denied