PLAN: Install Docusaurus
IMPLEMENTATION RULES: Before implementing this plan, read and follow:
- WORKFLOW.md - The implementation process
- PLANS.md - Plan structure and best practices
Status: Completed (2026-05-12)
Goal: Get Docusaurus building and serving Atlas's existing website/docs/ content locally, then ship it to GitHub Pages with custom domain atlas.sovereignsky.no (mirrors UIS's uis.sovereignsky.no pattern).
Last Updated: 2026-05-11
Investigation: INVESTIGATE-deployment-pipeline.md — this is the first PLAN spawned from it; closes the "Docusaurus skeleton + deploy" item in the recommended v1 priority order. Subsequent PLANs handle Scalar-at-/api and dbt-docs-at-/lineage/.
Prerequisites: None. Atlas-side install can ship before UIS-side deploy wiring is ready.
Reference: The UIS repo runs the same Docusaurus pattern at urbalurba-infrastructure/website/. Copy config conventions, version pins, and deploy shape from there. The conventions are already captured in atlas/website/README.md — this PLAN executes them.
Problem Summary
website/docs/ has real content (ai-developer guides, contributors guides, the public docs scaffold) but no renderer. Browsing is GitHub-only. Per the deployment-pipeline INVESTIGATE, the v1 public Atlas surface is a single Docusaurus site at atlas.sovereignsky.no that will later host Scalar at /api and dbt-docs at /lineage/. This PLAN gets the bare skeleton up — landing page, navigation, search — so the follow-on PLANs have something to mount their pages into.
Phase 1: Local install + config
Get npm run start working in website/ showing existing docs/ content with the UIS-matched conventions (Docusaurus 3.9.2, TypeScript config, search, mermaid).
Tasks
- 1.1 Create
website/package.jsonwith"name": "atlas-website","private": true, the script block (start,build,serve,clear,typecheck,swizzle), and the dependency set pinned to UIS's versions:Dev deps:@docusaurus/core 3.9.2
@docusaurus/preset-classic 3.9.2
@docusaurus/theme-mermaid 3.9.2
@easyops-cn/docusaurus-search-local ^0.46.1
@mdx-js/react ^3.0.0
clsx ^2.0.0
docusaurus-plugin-image-zoom ^2.0.0
prism-react-renderer ^2.3.0
react ^18.3.1
react-dom ^18.3.1@docusaurus/module-type-aliases 3.9.2,@docusaurus/tsconfig 3.9.2,@docusaurus/types 3.9.2,typescript ~5.6.2. Engines:"node": ">=18.0". - 1.2 Create
website/docusaurus.config.tsmodelled onurbalurba-infrastructure/website/docusaurus.config.ts, with Atlas-specific fields:title: 'Atlas'tagline: 'Open semantic layer over Norwegian public data'(or similar — draft, refine in 1.7)url: 'https://atlas.helpers.no'baseUrl: '/'organizationName: 'helpers-no',projectName: 'atlas'onBrokenLinks: 'warn',onBrokenAnchors: 'warn'markdown.mermaid: true,markdown.format: 'detect'themes: ['@docusaurus/theme-mermaid']- Plugins:
docusaurus-plugin-image-zoom+@easyops-cn/docusaurus-search-local(same config block as UIS) - No blog preset for now (Atlas isn't running a blog). Drop the
blog: {...}block from the UIS template. - Navbar items:
Docs(left),GitHublink (right). SkipServicesandBlogitems. - Prism: same set of additional languages —
bash, yaml, json, typescript, pythonplussql(relevant for Atlas).
- 1.3 Create
website/sidebars.tswith a hand-curatedtutorialSidebarcovering the existingdocs/shape:index,about/*,sector/*,getting-started/*,concepts/*,measurements/*,sources/*,contributors/*,ai-developer/*(useautogeneratedwith adirNameper category). Match the UIS pattern where each top-level category usesautogeneratedagainst the matching folder. - 1.4 Create
website/tsconfig.jsonextending@docusaurus/tsconfig. - 1.5 Create
website/src/css/custom.css— minimal stub (Atlas can refine later). Include just the default Docusaurus CSS variables for now. - 1.6 Create
website/static/with a placeholderimg/favicon.ico(can use a transparent 1×1 ICO or the Docusaurus default; replace later with branded asset). - 1.7 Make sure
website/docs/index.mdworks as the docs landing page. If it doesn't exist yet, add a minimal one ("Atlas — open semantic layer over Norwegian public data. Browse the docs in the sidebar."). - 1.8 Create
website/.gitignoreto excludenode_modules/,build/,.docusaurus/,.cache-loader/. - 1.9
cd website && npm install— let it resolve. Commitpackage.json+package-lock.json. - 1.10
npm run start— verify the dev server boots on port 3000 and renders the landing page + sidebar navigation. Click through into ai-developer/, contributors/, etc.; confirm pages render. - 1.11 Fix any
onBrokenLinks: 'warn'warnings in the existing markdown that surface during build — minimum: nothing that's actually broken. Acceptable to leave warnings that point at files added in later PLANs.
Validation
npm run start runs in website/; the dev server at http://localhost:3000 renders the landing page + sidebar, and clicking into existing docs/ content (e.g. ai-developer/PLANS.md, contributors/setup.md) navigates correctly. Search box at the top works. Mermaid diagrams in any existing markdown render.
Phase 1 outcome (2026-05-11)
Shipped. npm run build succeeds; npm run serve returns 200 on /, /about/what-is-atlas, /contributors/setup, /ai-developer/PLANS.
Three deviations from the PLAN as written, each justified:
- Webpack pin via
overrides— Atlas's fresh install resolved webpack5.106.2, which has a ProgressPlugin schema regression incompatible withwebpackbar(the build fails withoptions has an unknown property 'name' / 'color' / 'reporters'). UIS's lockfile resolves to5.104.1which works. Added"overrides": { "webpack": "5.104.1" }topackage.jsonto match. Worth revisiting when Docusaurus releases a build that pins webpack itself. - Stub
index.mdadded toconcepts/,measurements/,sources/— task 1.3 assumedautogeneratedsidebars would work for empty category folders (they have_category_.jsonbut no markdown). They don't; Docusaurus emitsNo docs found in 'X': can't auto-generate a sidebar. Added a placeholderindex.mdin each — same "in progress" framing the README already uses for these categories. - Docs routed at
/, not/docs—routeBasePath: '/'in the docs preset (plusdocsRouteBasePath: '/'in the search plugin config). Without it,https://atlas.helpers.no/would 404 and only/docs/*would resolve. Atlas is a docs-only site (no blog, no services pages), so the docs are the site — make/work. URLs simplify accordingly:/about/what-is-atlasrather than/docs/about/what-is-atlas. Does not conflict with the planned/api(Scalar) or/lineage/(dbt-docs) subpaths because Docusaurus only claims routes that exist indocs/.
Existing markdown emits ~30 "broken link" and ~10 "broken anchor" warnings — links written for GitHub rendering ([x](path/to/file.md)) that don't resolve cleanly under Docusaurus. onBrokenLinks: 'warn' lets the build pass. These get cleaned up incrementally in content PLANs; not blocking Phase 2.
The favicon task (1.6) was effectively skipped — no favicon field set in docusaurus.config.ts, so Docusaurus uses its built-in default. Branded favicon lands later with the rest of brand assets.
Phase 2: Production build + CI gate
Build the site as static HTML, validate it in CI on every PR.
Tasks
- 2.1
cd website && npm run build— verify the production build succeeds and produceswebsite/build/withindex.html, hashed JS/CSS, and per-page HTML. - 2.2
npm run serve— verify the built static site serves correctly athttp://localhost:3000(catches any "works in dev, breaks in prod" Docusaurus quirks). - 2.3 Add a GitHub Actions workflow at
.github/workflows/website-build.yml(or extend an existing one) that runsnpm install && npm run typecheck && npm run buildon every PR and push tomain, inwebsite/. No deploy yet — just the build gate. Pinnode-version: 20. - 2.4 Verify the workflow passes on a PR.
Validation
PRs touching website/ get a green CI check that proves npm run build succeeds.
Phase 2 outcome (2026-05-12)
2.1 + 2.2 passed during Phase 1 verification; not re-run for Phase 2 because the state is unchanged. 2.3 shipped: .github/workflows/website-build.yml runs npm ci + typecheck + build on every PR and push to main that touches website/** or the workflow file itself. Pinned to node-version: 20, with npm cache keyed on website/package-lock.json — matches the pattern UIS uses in their own docs.yml. permissions: contents: read (no writes needed for a build-only gate). No deploy step here — that's Phase 3.
One deviation from the PLAN: 2.4 (verify the workflow passes on a PR) is deferred to when the branch ships. The workflow can't run until the branch is pushed to origin and a PR is opened. That's a single push + gh pr create once we're ready to ship Phase 2, not work the implementation needs to wait on.
The strict onBrokenLinks: 'throw' / onBrokenAnchors: 'throw' config from Phase 1's link-cleanup pass means this CI gate automatically catches any future broken markdown link or anchor — no separate guard needed.
Phase 3: Deploy to GitHub Pages
Pivot from "Docker image on UIS" to "GitHub Pages with custom domain" — matches the sister-project pattern (urbalurba-infrastructure ships its docs at uis.sovereignsky.no via the same path). Static-site docs don't need UIS's Kubernetes/Traefik/observability; the dynamic services (PostgREST, Dagster, atlas-data) stay on UIS.
Hostname: atlas.sovereignsky.no for v1, mirroring uis.sovereignsky.no. The original atlas.helpers.no target via UIS Traefik is deferred — possibly a future migration once helpers.no DNS is in our control, possibly never if sovereignsky.no stays the docs home.
Tasks
- 3.1 Add the GitHub Pages deploy job to
.github/workflows/website-build.yml:- Trigger: same as the build job (PR + push to
main+workflow_dispatch), but the deploy step runs only onpushtomain(use anif:guard). - Pattern: mirror UIS's
docs.yml—actions/upload-pages-artifact@v3after the build, then adeployjob that runsactions/deploy-pages@v4. - Permissions:
contents: read, pages: write, id-token: write(Pages deploy requires the OIDC token to authenticate). - Concurrency:
group: "pages", cancel-in-progress: false— only one deploy at a time, queue the rest.
- Trigger: same as the build job (PR + push to
- 3.2 Create
website/static/CNAMEcontaining the single lineatlas.sovereignsky.no— Docusaurus copies the contents ofstatic/to the build root, so the deployed site has aCNAMEfile at the root that GitHub Pages reads to set the custom domain. - 3.3 Repo-settings step:
- Repo flipped public via
gh api -X PATCH /repos/terchris/atlas -f visibility=public(free-plan GitHub Pages requires public repo). - Pages enabled via
gh api -X POST /repos/terchris/atlas/pages -f build_type=workflow. - Custom domain registered via
gh api -X PUT /repos/terchris/atlas/pages -f cname=atlas.sovereignsky.noafter first deploy.
- Repo flipped public via
- 3.4 DNS step: CNAME
atlas.sovereignsky.no→terchris.github.ioadded at user's DNS provider. Verifieddig atlas.sovereignsky.no +shortreturns the GitHub Pages IPs185.199.108-111.153. - 3.5 Verified after first deploy:
https://terchris.github.io/atlas/→ 200 (default project-pages URL).https://atlas.sovereignsky.no/→ live after Let's Encrypt cert provisions (a few minutes after CNAME registration).
Validation
https://atlas.sovereignsky.no/ is live and serves the Atlas Docusaurus site. A content change in website/docs/ → merged to main → workflow builds + deploys → visible at atlas.sovereignsky.no end-to-end.
Phase 3 outcome (2026-05-12)
Shipped end-to-end. Workflow mirrors UIS's docs.yml but stays one file (build + deploy with an if: guard so PRs build-only and only main pushes upload + deploy). Permissions extended to pages: write + id-token: write as required by actions/deploy-pages@v4. Concurrency group pages so deploys queue if main commits land back-to-back.
docusaurus.config.ts url field updated to https://atlas.sovereignsky.no so the sitemap, OG tags, and canonical URLs match the deploy target.
Two surprises during user-side setup:
- Repo was private at start of Phase 3 — GitHub Pages on the free plan requires public repos. Flipped to public after explicit user consent. Consistent with sister projects (urbalurba-infrastructure and devcontainer-toolbox are both public).
CNAMEfile ≠ Pages custom-domain config when source isworkflow— withbuild_type: workflow, theCNAMEfile in the deployed artefact does not auto-configure the Pages custom-domain field. (It does in branch-source mode.) Had to set the custom domain explicitly viagh api -X PUT /repos/terchris/atlas/pages -f cname=atlas.sovereignsky.noafter the first deploy. Thewebsite/static/CNAMEfile we ship is still useful documentation in the artefact but it's not load-bearing for the actual Pages config. Worth noting for any future Docusaurus install following the same pattern.
Acceptance Criteria
-
cd website && npm run startshows the Atlas Docusaurus site locally with existingdocs/content navigable. -
npm run buildproduces a working static site inwebsite/build/. - CI runs
npm run typecheck+npm run buildon every PR. - CI on main builds + deploys the site to GitHub Pages.
-
https://atlas.sovereignsky.no/is live and serves the Docusaurus site.
Implementation Notes
- Convention reference: the existing
atlas/website/README.mdis the source of truth for "what conventions are inherited from sister Helpers sites" (Docusaurus 3.9.2, TS config, search plugin choice, etc.). Follow it; don't re-litigate. - Pivot from UIS-served to GitHub Pages (2026-05-12): earlier drafts of this PLAN assumed a Docker image + UIS Traefik IngressRoute. Switched to GitHub Pages with custom domain
atlas.sovereignsky.noafter recognising the sister-project pattern (UIS itself shipsuis.sovereignsky.nothis way). Trade-offs:- Pro: zero infra cost; no Dockerfile to maintain; no UIS-side coordination needed; automatic HTTPS via Let's Encrypt; matches what sister projects already do.
- Con: docs are no longer "sovereign-stack-served." Acceptable because static docs don't need K8s/observability — the dynamic services that do (PostgREST, Dagster, atlas-data) remain on UIS.
- The original UIS-served path is deferred, not deleted — could revisit if/when there's a reason to move docs onto helpers.no via UIS.
- No
developer-atlas.helpers.no: per the INVESTIGATE rewrite (2026-05-11), Scalar and dbt-docs ship as pages inside this Docusaurus site (/apiand/lineage/), not separate hostnames. This PLAN does not wire up those pages; that's two follow-on PLANs. The Pages-served shape established here (single static site, single hostname) is the substrate they mount into. - Don't add blog/Services nav items: the UIS template has them; Atlas doesn't need them. Keep the navbar lean.
- Mermaid is on but optional: existing Atlas markdown doesn't use Mermaid yet. Enabling it now means future plans can drop diagrams in without config changes.
- Search index size: with current
docs/content (~50 markdown files),@easyops-cn/docusaurus-search-local's index builds in seconds and adds a few hundred KB to the bundle. Fine. Revisit if the docs corpus grows 10×.
Files to Modify / Create
Create:
website/package.jsonwebsite/package-lock.json(generated)website/docusaurus.config.tswebsite/sidebars.tswebsite/tsconfig.jsonwebsite/.gitignorewebsite/.dockerignorewebsite/src/css/custom.csswebsite/static/CNAME— single lineatlas.sovereignsky.nofor GitHub Pages custom domain.website/docs/index.md(if missing).github/workflows/website-build.yml— build gate + Pages deploy job.
Modify:
website/README.md— short addendum: "Run locally:npm install && npm run startin this folder." Drop the "Docusaurus is not yet installed" preamble once Phase 1 ships.- Top-level
README.md(Atlas root) — add a link tohttps://atlas.sovereignsky.noonce Phase 3 ships.
Do not touch:
- Anything under
urbalurba-infrastructure/. The Docusaurus deploy no longer involves UIS at all.