Skip to content

Documentation Site Design

Date: 2025-12-29 Status: Approved

Overview

Replace the dual-layer documentation architecture (Git markdown + Notion) with a single-source MkDocs Material site hosted on Cloudflare Pages with Access protection.

Problem Statement

Current pain points: - Sync effort — Manually keeping Git docs and Notion updated - Search friction — Finding things across two systems - Context switching — Jumping between Notion and code - Notion API limitations — Breakage, permissions issues, orphaned content

Solution

Architecture

┌─────────────────────────────────────────────────────────────────────┐
│                         docs/ (Single Source)                       │
│  ├── getting-started/   ├── operations/   ├── reference/           │
│  ├── architecture/      └── plans/                                  │
└───────────────────────────────┬─────────────────────────────────────┘
                                │
            ┌───────────────────┼───────────────────┐
            ▼                   ▼                   ▼
    ┌──────────────┐    ┌──────────────┐    ┌──────────────┐
    │  MkDocs      │    │  CLAUDE.md   │    │  GitHub      │
    │  Material    │    │  (routing)   │    │  (source)    │
    │  → CF Pages  │    │  → AI reads  │    │  → git ops   │
    └──────────────┘    └──────────────┘    └──────────────┘
            │
            ▼
    ┌──────────────────────────────────────┐
    │  Cloudflare Access                   │
    │  ├── Authentik (SSO)                 │
    │  ├── GitHub (convenient)             │
    │  └── OTP (break-glass)               │
    │  Restricted: fzymgc.email/house/dev, │
    │              fuzzymagic.com          │
    └──────────────────────────────────────┘

Components

Component Technology Purpose
Static site generator MkDocs + Material Build searchable docs from markdown
Hosting Cloudflare Pages Auto-deploy on push
Access control Cloudflare Access SSO via 3 IDPs + domain restriction
Authentik exposure Cloudflare Tunnel Make Authentik publicly reachable
CI/CD GitHub Actions Build + deploy workflow

Directory Structure

docs/
├── index.md
├── getting-started/
│   ├── index.md
│   ├── environment.md
│   ├── cluster-access.md
│   └── ai-tooling.md
├── operations/
│   ├── index.md
│   ├── vault.md
│   ├── authentik.md
│   ├── cloudflare.md
│   ├── hcp-terraform.md
│   ├── windmill.md
│   └── github-tokens.md
├── reference/
│   ├── index.md
│   ├── services.md          # replaces Notion Services Catalog
│   ├── technologies.md      # replaces Notion Tech References
│   ├── network.md
│   └── secrets.md
├── architecture/
│   ├── index.md
│   ├── overview.md
│   └── decisions/
└── plans/
    ├── index.md
    └── archive/

Configuration

MkDocs (mkdocs.yml)

site_name: fzymgc-house Cluster Docs
site_url: https://docs.fzymgc.house
repo_url: https://github.com/fzymgc-house/selfhosted-cluster
repo_name: fzymgc-house/selfhosted-cluster

theme:
  name: material
  features:
    - navigation.instant
    - navigation.tracking
    - navigation.sections
    - navigation.expand
    - search.suggest
    - search.highlight
    - content.code.copy
  palette:
    - scheme: slate
      primary: deep purple
      accent: amber
      toggle:
        icon: material/brightness-4
        name: Switch to light mode
    - scheme: default
      primary: deep purple
      accent: amber
      toggle:
        icon: material/brightness-7
        name: Switch to dark mode

plugins:
  - search
  - tags

markdown_extensions:
  - admonition
  - pymdownx.details
  - pymdownx.superfences
  - pymdownx.tabbed:
      alternate_style: true
  - tables
  - toc:
      permalink: true

nav:
  - Home: index.md
  - Getting Started:
    - getting-started/index.md
    - Environment Setup: getting-started/environment.md
    - Cluster Access: getting-started/cluster-access.md
    - AI Tooling: getting-started/ai-tooling.md
  - Operations:
    - operations/index.md
    - Vault: operations/vault.md
    - Authentik: operations/authentik.md
    - Cloudflare: operations/cloudflare.md
    - HCP Terraform: operations/hcp-terraform.md
    - Windmill: operations/windmill.md
    - GitHub Tokens: operations/github-tokens.md
  - Reference:
    - reference/index.md
    - Services Catalog: reference/services.md
    - Technologies: reference/technologies.md
    - Network: reference/network.md
    - Secrets & Vault Paths: reference/secrets.md
  - Architecture:
    - architecture/index.md
    - System Overview: architecture/overview.md
    - Decisions: architecture/decisions/
  - Design Plans:
    - plans/index.md
    - Archive: plans/archive/

GitHub Actions (.github/workflows/docs.yml)

name: Deploy Docs

on:
  push:
    branches: [main]
    paths:
      - 'docs/**'
      - 'mkdocs.yml'
      - '.github/workflows/docs.yml'
  workflow_dispatch:

jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      deployments: write

    steps:
      - uses: actions/checkout@v6

      - name: Setup Python
        uses: actions/setup-python@v6
        with:
          python-version: '3.12'

      - name: Install dependencies
        run: pip install mkdocs-material

      - name: Build docs
        run: mkdocs build --strict

      - name: Deploy to Cloudflare Pages
        uses: cloudflare/wrangler-action@v3
        with:
          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
          command: pages deploy site --project-name=cluster-docs

Cloudflare Access (tf/cloudflare/access.tf)

# Identity Providers
resource "cloudflare_zero_trust_access_identity_provider" "authentik" {
  account_id = var.cloudflare_account_id
  name       = "Authentik"
  type       = "oidc"

  config {
    client_id     = var.authentik_oidc_client_id
    client_secret = var.authentik_oidc_client_secret
    auth_url      = "https://auth.fzymgc.house/application/o/authorize/"
    token_url     = "https://auth.fzymgc.house/application/o/token/"
    certs_url     = "https://auth.fzymgc.house/application/o/cloudflare-access/jwks/"
    scopes        = ["openid", "email", "profile"]
  }
}

resource "cloudflare_zero_trust_access_identity_provider" "github" {
  account_id = var.cloudflare_account_id
  name       = "GitHub"
  type       = "github"

  config {
    client_id     = var.github_oauth_client_id
    client_secret = var.github_oauth_client_secret
  }
}

resource "cloudflare_zero_trust_access_identity_provider" "otp" {
  account_id = var.cloudflare_account_id
  name       = "Email One-Time PIN"
  type       = "otp"
}

# Access Application
resource "cloudflare_zero_trust_access_application" "cluster_docs" {
  account_id       = var.cloudflare_account_id
  name             = "Cluster Documentation"
  domain           = "docs.fzymgc.house"
  type             = "self_hosted"
  session_duration = "24h"

  allowed_idps = [
    cloudflare_zero_trust_access_identity_provider.authentik.id,
    cloudflare_zero_trust_access_identity_provider.github.id,
    cloudflare_zero_trust_access_identity_provider.otp.id,
  ]
}

resource "cloudflare_zero_trust_access_policy" "cluster_docs_allow" {
  account_id     = var.cloudflare_account_id
  application_id = cloudflare_zero_trust_access_application.cluster_docs.id
  name           = "Allow authorized users"
  precedence     = 1
  decision       = "allow"

  include {
    login_method = [
      cloudflare_zero_trust_access_identity_provider.authentik.id,
      cloudflare_zero_trust_access_identity_provider.github.id,
      cloudflare_zero_trust_access_identity_provider.otp.id,
    ]
  }

  require {
    email_domain = [
      "fzymgc.email",
      "fzymgc.house",
      "fzymgc.dev",
      "fuzzymagic.com",
    ]
  }
}

Tunnel Config (additions to tf/cloudflare/tunnel.tf)

# Add Authentik to tunnel ingress
variable "authentik_services" {
  description = "Authentik endpoints to expose via tunnel"
  type = map(object({
    service_url = string
  }))
  default = {
    auth = {
      service_url = "https://authentik-server.authentik.svc.cluster.local"
    }
  }
}

# Modify ingress concat to include:
[for svc, definition in var.authentik_services : {
  hostname = "${svc}.fzymgc.house"
  service  = definition.service_url
  origin_request = {
    http_host_header   = "${svc}.fzymgc.house"
    origin_server_name = "authentik-server.authentik.svc.cluster.local"
    no_tls_verify      = true
  }
}],

# DNS for Authentik
resource "cloudflare_dns_record" "authentik" {
  for_each = var.authentik_services

  zone_id = data.cloudflare_zone.fzymgc_house.id
  name    = each.key
  content = "${cloudflare_zero_trust_tunnel_cloudflared.main.id}.cfargotunnel.com"
  type    = "CNAME"
  proxied = true
  comment = "Authentik via ${var.tunnel_name} tunnel"
  ttl     = 1
}

Content Migration

Source Destination
docs/*.md (current) docs/operations/ or getting-started/
Notion Services Catalog docs/reference/services.md
Notion Tech References docs/reference/technologies.md
Notion Operations Guide Merge into docs/operations/*.md
Notion Quick Reference docs/reference/network.md + secrets.md
CLAUDE.md network info docs/reference/network.md

Secrets Required

Vault Path Keys
secret/fzymgc-house/cluster/cloudflare-access authentik_oidc_client_id, authentik_oidc_client_secret, github_oauth_client_id, github_oauth_client_secret

Implementation Phases

Phase Tasks
1. Foundation Create mkdocs.yml, reorganize docs/ structure, local preview
2. Content migration Move existing docs, create reference tables from Notion data
3. CI/CD GitHub Actions workflow, Cloudflare Pages project setup
4. Authentik exposure Tunnel config, DNS, create OIDC app in Authentik
5. Access protection Deploy Access application + policies
6. Cleanup Slim CLAUDE.md files, update repo documentation, deprecate Notion

CLAUDE.md Strategy

CLAUDE.md files become routing layers: - AI-specific constraints ("MUST NOT commit to main") - Tool/skill routing ("use Kubernetes MCP for pods") - Pointers to docs ("see docs/operations/vault.md") - Minimal — just enough to route AI to the right place

All substantive content lives in docs/ — written in AI-friendly format (tables, headers, code blocks).

Success Criteria

  • [ ] Full-text search works across all docs
  • [ ] Authentik SSO login functional
  • [ ] GitHub and OTP fallback working
  • [ ] Existing doc content migrated
  • [ ] Notion content consolidated
  • [ ] CLAUDE.md files slimmed to routing
  • [ ] Auto-deploy on push to main