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