ADR 0005 — Self-hosted knowledge base on CX43 behind Authelia
Context
Section titled “Context”Engineering and product documentation for the Cora Health platform grew from a handful of markdown files to approximately 20 documents totalling roughly 9,800 lines. Several files exceeded 2,400 lines (the platform roadmap, the iOS SDK PRD). The flat structure created practical problems:
- No in-document search; finding a specific config detail required grepping locally.
- No structured navigation; understanding the relationship between documents (e.g., which app PRD references which ADR) required reading multiple files.
- Cross-references were not validated; broken links went undetected.
- Frontmatter-based status fields (
status: in-progress,owner: josef) existed in files but were not rendered or queryable. - Sub-agents working on specific tasks read entire files to find relevant sections, consuming context window unnecessarily.
Requirements
Section titled “Requirements”The knowledge base must:
- Support full-text search across all documents.
- Render structured navigation (sidebar, section grouping, breadcrumbs).
- Honour frontmatter fields:
title,status,owner,last_reviewed,tags. - Be internal-only — content includes server runbooks, RLS schemas, pricing strategy, app architecture decisions, and other IP that must not be public.
- Run on existing infrastructure at zero marginal cost beyond storage.
- Be editable as plain markdown files in a GitLab repository, with no proprietary formats.
- Survive a contributor who forgets to close a JSX tag (no MDX-related build failures).
Infrastructure available
Section titled “Infrastructure available”CX43 already runs:
- Traefik as reverse proxy with Let’s Encrypt TLS
- Authelia (
auth.caloon.ventures) for SSO/2FA across hosted services - Dokploy for container deployment and management
- GitLab CI (self-hosted or GitLab.com) for builds
Adding a static site to this stack requires one new DNS record, one new Traefik routing rule, one new Authelia access policy, and a Dokploy application. No new infrastructure is needed.
Tool selection
Section titled “Tool selection”Starlight (built on Astro) was chosen as the site framework:
- Static output (HTML/CSS/JS) — no server process, trivial to containerise with nginx.
- Built-in sidebar navigation driven by file system structure.
- Pagefind integration for client-side full-text search with no external service dependency.
- Plain markdown (
.md) files — no MDX required, no custom components needed. - The homepage (
homepage/) already uses Astro 5 and Tailwind CSS 4; the team has existing familiarity with the Astro toolchain. - Default Starlight theme is functional and requires no customisation to be usable.
Decision
Section titled “Decision”Self-host a Starlight (Astro) static site on CX43, served at docs.cora.health, behind
Authelia one-factor authentication via Traefik ForwardAuth middleware.
Repository structure:
- Content lives in
docs/(pure markdown, organised by section). - Site renderer lives in
site/(Astro + Starlight config,astro.config.mjs, etc.). - Agents and humans edit only
docs/andtasks/in normal operations. AGENTS.mdat the repo root documents conventions for AI sub-agents.
Build and deployment:
- Multi-stage Docker image:
node:20-alpinebuild stage →nginx:unprivileged-alpineserving thedist/output. - GitLab CI pipeline (
.gitlab-ci.yml) runs on every push:- Validate frontmatter on all
.mdfiles (required fields present,statusis a known value,dateis ISO-8601). - Run
lycheelink checker to detect broken internal and external links. - Build the Astro site (
npm run build). - On
mainbranch: push the Docker image to GitLab Container Registry and trigger a Dokploy redeploy via webhook.
- Validate frontmatter on all
- Dokploy manages the running container: health checks, restart policy, log forwarding.
Access control:
- Traefik ForwardAuth middleware routes all requests to
docs.cora.healththrough Authelia before serving content. - Authelia policy:
one_factor(username + password) for all paths ondocs.cora.health. - Unauthenticated requests receive a redirect to the Authelia login portal.
- The Authelia instance at
auth.caloon.venturesis the existing shared instance on CX43.
Search:
- Pagefind generates a static search index at build time.
- The search index is served as static files alongside the site; no search backend.
- No Algolia DocSearch, no external search API, no telemetry to third parties.
Consequences
Section titled “Consequences”Infrastructure
- One DNS A record:
docs.cora.health → CX43 public IP. - TLS via Let’s Encrypt through Traefik (the existing cert-manager on CX43 handles this automatically once the DNS record resolves).
- No new VPS, no new managed service, no additional monthly cost.
Authelia access upgrade path
Upgrading to two-factor auth for docs.cora.health requires one line change in the Authelia
access control policy (one_factor → two_factor). No infrastructure change is needed.
Group-based policies can restrict specific sections (e.g., server runbooks) to a subset of
users if needed in future.
Public SDK docs option
If the CoraHealthKit SDK goes open-source, a public slice of the docs (SDK reference only)
can be served without Authelia by adding a second Traefik routing rule without the ForwardAuth
middleware, scoped to a path prefix (e.g., /sdk/). Authelia’s group-based ACLs support
this without re-architecting the site.
Content conventions
Every markdown file must include valid frontmatter with the fields defined in the ADR file
format. The CI frontmatter validator enforces this on every push. Files failing validation
block the build and cannot reach main.
Limitations
- No real-time collaboration (no Google Docs-style simultaneous editing). Edits go through GitLab MRs.
- Search index is rebuilt on every deploy; very large sites (thousands of pages) may see slow build times. Not a concern at current scale (~200 pages projected at full build-out).
- No public API or programmatic access to docs content. Sub-agents read from the filesystem, not from the deployed site.
Alternatives considered
Section titled “Alternatives considered”Notion / Confluence
Both offer rich editing, inline comments, and strong search. Rejected because:
- Content is stored on third-party infrastructure. Health platform IP (server configs, RLS schemas, pricing strategy) should not leave infrastructure under direct control.
- Notion’s free tier has block limits; Confluence requires a paid plan at team scale.
- Neither provides a git-native workflow — edits are not tracked as commits, cannot be reviewed via MR, and cannot be validated by CI.
GitBook
Similar to Notion: polished UI, good search, reasonable markdown support. Rejected for the same data residency reason. GitBook’s free tier is public-only; private spaces require a paid plan and still store content on GitBook’s infrastructure.
GitHub / GitLab repository wiki
Zero cost, git-native, no external service. Rejected because:
- No structured navigation beyond a flat page list.
- No full-text search (GitLab wiki search is limited).
- No frontmatter rendering or status tracking.
- Not suitable for 200+ files with cross-references.
Plain GitLab repo browsing
The current state. Rejected because it does not address any of the navigation, search, or cross-reference validation requirements that motivated this decision.
Hosted Starlight on Netlify or Vercel
Would eliminate the hosting operational burden. Rejected because:
- The site content (runbooks, schemas, architecture decisions) must not be accessible to third parties, and Netlify/Vercel would need to be trusted with the built output.
- Netlify/Vercel access control (password protection or team SSO) adds a per-seat cost and a third provider dependency.
- CX43 already has the Traefik + Authelia + Dokploy stack; the marginal operational cost of adding a static site container is negligible compared to onboarding a new hosting provider.