Skip to main content

Dev Docs Access: Separate CF Pages Site + Cloudflare Access

Decision

Replace the single mixed Docusaurus site with two separate Cloudflare Pages deployments:

SiteURLAudienceProtection
Public docsdocs.rat.gdEnd usersNone
Dev docsdev-docs.rat.gdContributorsCloudflare 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.

  1. Go to github.com/organizations/new
  2. Choose the Free plan
  3. Set organization name to GDPR-Labsthis 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 the organizationName field in website/docusaurus.config.ts is gdprlabs — that field is GitHub Pages deploy metadata only and does not need to match.
  4. Skip the "add members" step for now — you'll invite them after setup
  5. Once created, go to github.com/GDPR-LabsPeopleInvite member
  6. Invite each current repo collaborator by their GitHub username
  7. 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.

  1. Go to github.com/settings/developersOAuth AppsNew 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 enter https://dev-docs.rat.gd as a placeholder — Phase 3 step 5 replaces it with the real callback URL
  2. Click Register application
  3. On the next page, note the Client ID
  4. 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

  1. Go to one.dash.cloudflare.com → select your account → Zero Trust
  2. SettingsAuthenticationLogin methodsAdd newGitHub
  3. Paste the Client ID and Client Secret from Phase 2
  4. Copy the Callback URL shown by Cloudflare (format: https://<your-team>.cloudflareaccess.com/cdn-cgi/access/callback)
  5. Go back to the GitHub OAuth App (github.com/settings/developers → your app) and paste that URL into Authorization callback URLUpdate application
  6. Back in CF Zero Trust, click Save
  7. Click Test — a GitHub auth popup should open, you should see a success result

3b. Create the Access Application

  1. In Zero Trust → AccessApplicationsAdd an applicationSelf-hosted
  2. Fill in:
    • Application name: ROPA 2.0 Dev Docs
    • Session duration: 24 hours (adjust to taste)
    • Application domain: dev-docs.rat.gd
  3. Click Next
  4. Under PoliciesAdd 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)
  5. Click NextAdd 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: falsepages: false is 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-DevDocsIcon navbar 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:dev simultaneously — both configs share the same .docusaurus working directory.


Phase 5 — Create the Cloudflare Pages project

  1. Go to dash.cloudflare.comWorkers & PagesCreatePagesConnect to Git
  2. Authorize Cloudflare to access the repo, select it
  3. 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
  4. Click Save and Deploy — wait for the first build to complete
  5. In the project → SettingsBuilds & deploymentsBuild watch paths, set Include paths to website/* and docs/dev/* so pushes that touch neither skip the build
  6. On the existing public docs project, set Include paths to website/* and docs/user/* (note: content lives in docs/, outside website/, so watching website/* alone misses content edits; keep docs/* until Phase 6 removes /dev/* from the public site)

5a. Add the custom domain

  1. In the Pages project → Custom domainsSet up a custom domain
  2. Enter dev-docs.rat.gd
  3. Cloudflare will add the DNS record automatically (since the domain is already on Cloudflare)
  4. 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:

  1. In the Pages project → SettingsAccess policies
  2. Click Manage (this links to the Access Application created in Phase 3b)
  3. Confirm ROPA 2.0 Dev Docs is 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.ts references 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.gd in an incognito window → Cloudflare Access login screen appears (not the docs)
  • Log in with a GitHub account that is not in the GDPR-Labs org → access denied
  • Log in with a GitHub account that is in the GDPR-Labs org → 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-Labs org → their existing CF Access session expires within the configured session duration; new login attempt is denied