Skip to main content

Docusaurus Docker Publishing

Use when publishing to the Dockerized Docusaurus docsite (docs, tags, git sync). This host runs a pinned Docusaurus 3.10.2 instance in Docker: project at /root/Working/Projects/docsite, container name docsite, image docsite:latest, port 3000. Docs live in docsite/docs/ (auto sidebar; subdirectories become categories). Docker-only install is a standing user requirement. Global owner rule: every artifact generated for owner review (reports, briefs, review packets, receipts, deliverables, work records) is published to this site before it is considered delivered — drop it in the right category, rebuild, link it in the review message; the nightly docs-push cron commits and pushes automatically.

Naming convention (user-set, standing)​

Reports are generated by date: one file per report named YYYY-MM-DD-slug.md in docs/01-reports/ (front matter id matches the filename). Reports carry no sidebar_position — filename sort makes them chronological, so the date is the primary axis and new reports land in their date slot automatically. tags: from docs/tags.yml are the secondary index. Do not add one tag per report — only stable cross-cutting vocabulary.

  • Top-level categories display alphabetically. Each category's index.md carries sidebar_position: N = its alphabetical slot (Architecture→Catalogs→Compliance→…). To change display order, change that position — never renumber the NN- folder, which is storage-only (stable folder names) and no longer drives display order. A sidebar that reads as neither alphabetical nor numbered is the failure symptom of missing/tied category positions.
  • Within a category: by filename. Content docs omit sidebar_position (Docusaurus auto-sort); dated reports therefore sort chronologically. Do not add per-report positions — they fight the date order.
  • Verify the rendered order, not the folder names. Read the actual menu order out of build/docs/index.html (the SSR <nav class="…menu…"> block, .menu__link hrefs in DOM order) or the live DOM .menu__list-item sequence, and assert top-level labels are alphabetical. Folder structure can look right on disk while the sidebar renders out of order.

Publishing a document (most common task)​

  1. Drop the .md/.mdx into docsite/docs/ (front matter id/title controls URL slug; sidebar_position controls order; follow the date naming convention above for reports). Large external reports: verify the body is MDX-safe first (grep for raw HTML elements and {{/}} expression pairs — none present means safe), then prepend the front matter with a script and assert the body SHA-256 equals the source's so nothing is mangled in transit.
  2. Rebuild inside the running container:
    cd /root/Working/Projects/docsite && docker-compose exec -T docsite npm run build
    -T is required — without it compose fails with the input device is not a TTY and the build silently does not run. docs/ is a bind mount, so no image rebuild or container restart is needed; the build takes ~10–30 s. Note the container serves the rebuilt build/ itself, so the new doc is live after the build with no restart.
  3. Verify: curl -s -o /dev/null -w '%{http_code}' http://localhost:3000/docs/<category>/<slug> → 200, and grep the served HTML for a distinctive content fragment. Do not rely on the page's <title> — it can be empty in a shell render even when the body serves correctly; the fragment grep is the real check.
  4. Commit + push — every doc change MUST land in the private jknash/docsite repo:
    python3 /root/.hermes/scripts/docs-sync.py
    It stages docs/ + site config, auto-writes a commit message, and pushes with the token read from ~/.hermes/.env in-process (never on disk/argv); it silently no-ops when nothing changed. Verify the remote head via the GitHub Contents API with the jknash token — plain git ls-remote 403s because the global credential helper routes to the read-only gh OAuth token; that does not mean the push failed.

To delete a doc: remove the file, rebuild, confirm the URL now 404s.

Routes and URLs​

  • The /docs root 404s until a document with id index (docs/index.md) exists — add it so the docs section has a landing page.
  • Numbered-prefix category folders get their prefix stripped from routes: Docusaurus' numberPrefixParser drops NN- from folder names, so docs/01-reports/foo.md serves at /docs/reports/foo, NOT /docs/01-reports/foo. When adding or renaming a category, curl the stripped slug and link with it from the navbar and from cross-doc links — links written with the numeric prefix silently 404.

Rebuilding the image (config/Dockerfile changes)​

cd /root/Working/Projects/docsite && docker-compose build && docker-compose up -d

Pitfall: this host runs docker-compose 1.29 against a newer Docker engine, and post-rebuild container recreation fails with KeyError: 'ContainerConfig' — it does not recover with docker rm -f + up -d retry loops. Working path: recreate with plain Docker, reusing the compose network and binding docs/ and src/ mounts: docker rm -f docsite; docker run -d --name docsite --network docsite_docsite_net -p 3000:3000 -v /root/Working/Projects/docsite/docs:/app/docs -v /root/Working/Projects/docsite/src:/app/src docsite:latest. Verify with a healthcheck curl, then npm run build inside the container.

Pitfall: the host's Docker default subnet pools are exhausted (every 172.x/16 and 192.168/20 default range is taken by other networks), so docker-compose up fails with all predefined address pools have been fully subnetted. The compose file must pin its own bridge network with an explicit ipam.config subnet (this project uses 10.40.0.0/24). If a new Docker app on this host hits the same error, list existing network subnets and assign a free private range the same way — never remove or repurpose other projects' networks.

Theme and custom pages​

Site-wide theming lives in src/css/custom.css (dark slate base, cyan accent; the /portals page theme is hardcoded in src/pages/portals.js — to match a portals color, grep the hex in that file, not in the generator script, which contains no colors). src/ is bind-mounted, so editing CSS or pages needs only the in-container npm run build (step 2 above) — no image rebuild. Verify a color/style change by grepping the compiled bundle: grep -c <hex> build/assets/*.css (host-side build/ only appears after the in-container build writes it through the mount), then curl the served CSS through the tailnet URL.

The site is tailnet-only. From the host, http://localhost:3000 works; for live external verification use curl -sk --resolve jkdev001.tail6817df.ts.net:8445:100.70.216.68 https://jkdev001.tail6817df.ts.net:8445/<path> (skip cert check — tailscale-serve cert), or just confirm on localhost plus the compiled-bundle grep.

Docusaurus config rules (config validation fails the build)​

  • Docusaurus rejects unrecognized top-level config fields with a hard error. onNoImages and top-level colorMode are NOT valid site config fields (color mode lives in themeConfig.colorMode).
  • onBrokenMarkdownLinks at top level is deprecated; put it under markdown.hooks.onBrokenMarkdownLinks.
  • Keep custom pages (src/pages/) static: importing @docusaurus/useContext or @docusaurus/core from a page fails the client bundle with Module not found on this setup. @docusaurus/Link is fine. Hard-code the title instead of reading site config.
  • The healthcheck in the Dockerfile must probe a URL that reliably returns 200: probe /, not /docs/ (404 when no doc sits at the docs root — a failing probe flips the container to unhealthy while the site itself is fine).

Tags (supported out of the box, no plugin)​

Docusaurus docs tags work in the pinned 3.10.2 install with zero config changes: define tag metadata in docs/tags.yml (key → label/description), then reference keys from any doc's front matter: tags: [key1, key2]. Each tag auto-generates an index page at /docs/tags/<key> listing every doc carrying it; the build emits build/docs/tags/<key>/ (inside the container only — host-side build/ does not exist, see Routes). Keep tags to genuinely reusable cross-cutting buckets the user named; per-report or one-off tags fragment the tag index into singletons — the user prefers a small, stable vocabulary.

Scaffolding a fresh instance​

See references/scaffold.md for the full known-good file set (package.json with pinned 3.10.2 versions, docusaurus.config.js, sidebars.js, static index.js, multi-stage Dockerfile on node:22-alpine, docker-compose.yml with pinned subnet, .dockerignore). Copy, pin the same versions, and adjust the subnet if it collides.


Source: jknash/hermes-shared-skills · branch hermes-jkdev001 @ 1d0d545c3970 · skills/docusaurus-docker-publishing/ · view source · Imported 2026-10-04. Supporting files (references, scripts) remain in the source repository.

Published by Muse · 2026-10-04.