Skip to main content

Docusaurus — Internal Documentation Site

Project: Docusaurus Host: docusaurus / docusaurus.home.lab IP: 10.69.20.77 (VMID 277, VLAN 20) OS: Rocky Linux 10 (LXC, unprivileged) Public URL: https://docs.home.helix9.org Source repo: Docusaurus on OneDev

Overview

This documentation site itself. Docusaurus generates static HTML from Markdown source in OneDev. The output is served by a stripped-down nginx:alpine container on the docusaurus LXC. A push to the source repo triggers an OneDev webhook that rebuilds and redeploys the site within ~60 seconds.

Architecture

push
laptop ──────────────────────────────────► OneDev (10.69.20.76:6611)

│ webhook POST
│ http://10.69.20.77:9000/hooks/docusaurus-rebuild
│ (X-OneDev-Signature: <secret>)

┌───────────────────────── docusaurus LXC ───────────────────────────┐
│ │
│ webhook (systemd, port 9000) │
│ └─► /usr/local/bin/docusaurus-rebuild │
│ ├─ git fetch / git reset --hard origin/main │
│ ├─ podman run --rm node:20-alpine │
│ │ npm ci && npm run build → /srv/docusaurus/build │
│ └─ systemctl restart docusaurus.service │
│ │
│ docusaurus.service (Quadlet, nginx:alpine, port 8080) │
│ Volume: /srv/docusaurus/build → /usr/share/nginx/html (ro) │
│ │
└────────────────────────────────────────────────────────────────────┘

HTTPS │
browser ─────────────────────────► Traefik (10.69.20.40:443)
docs.home.helix9.org ↳ http://10.69.20.77:8080

Components

ComponentWhereRole
nginx:alpine (Quadlet)/etc/containers/systemd/docusaurus.containerServes /srv/docusaurus/build on port 8080
node:20-alpine (ephemeral)run on demand by rebuild scriptBuilds Docusaurus → static HTML
webhook binary/usr/local/bin/webhook (adnanh/webhook v2.8.2)HTTP listener, validates token, runs script
docusaurus-rebuild script/usr/local/bin/docusaurus-rebuildgit fetch + build + restart
docusaurus-webhook.service/etc/systemd/system/Runs the webhook listener on :9000
Hooks config/etc/webhook/hooks.jsonTrigger rule + secret + execute-command
Shared secret/etc/webhook/secret (mode 0600, 64 hex chars)Auth token, generated on first deploy

Source layout

docs/
├── intro.md # landing (slug: /)
├── networking/ # VyOS routers
│ └── _category_.json # category index + slug
├── automation/ # Terraform + Ansible
│ └── _category_.json
└── services/ # apps on PVE02 (this file lives here)
└── _category_.json

docusaurus.config.js — site title, navbar, footer, theme, search plugin. sidebars.js — auto-generated from filesystem; ordering via sidebar_position in frontmatter and position in _category_.json.

Adding a new doc

  1. Create docs/<category>/my-page.md with frontmatter:
    ---
    sidebar_label: "Short Label"
    sidebar_position: 5
    ---

    # My Page Title

    Content...
  2. Optional: cross-doc links use relative paths (./other-doc.md or ../category/doc.md).
  3. Test locally:
    cd ~/code/docusaurus
    npm start # live preview at http://localhost:3000
  4. Commit and push:
    git add .
    git commit -m "docs: add my page"
    git push
  5. Wait ~60 s for the webhook to rebuild. Refresh https://docs.home.helix9.org.

Local development

cd ~/code/docusaurus
npm install # one-time
npm start # http://localhost:3000 with hot reload
npm run build # produces /build (use to verify before push)

npm run build fails on broken markdown links (config: onBrokenLinks: 'warn' — change to 'throw' to enforce in CI later).

Webhook configuration

OneDev project Docusaurus → Settings → Web Hooks:

FieldValue
URLhttp://10.69.20.77:9000/hooks/docusaurus-rebuild
Secretcontent of /etc/webhook/secret on docusaurus LXC
Event TypesCode push only

OneDev sends the secret literally in the X-OneDev-Signature header — adnanh/webhook validates with a value match (not HMAC).

Manual operations

TaskCommand (on pve02 unless noted)
Force rebuildansible-playbook playbooks/docusaurus.yml -e docusaurus_force_rebuild=true
Trigger rebuild via webhook (test)curl -X POST -H "X-OneDev-Signature: $(ssh 10.69.20.77 cat /etc/webhook/secret)" http://10.69.20.77:9000/hooks/docusaurus-rebuild
Tail webhook logssh 10.69.20.77 'journalctl -fu docusaurus-webhook'
Tail nginx (docusaurus) logssh 10.69.20.77 'journalctl -fu docusaurus.service'
Restart nginxssh 10.69.20.77 'systemctl restart docusaurus.service'
Restart webhook listenerssh 10.69.20.77 'systemctl restart docusaurus-webhook.service'
Inspect build outputssh 10.69.20.77 'ls -la /srv/docusaurus/build'

Ansible role

Source: roles/docusaurus/ in the Ansible repo. Key files:

  • tasks/main.yml — install git, podman pulls, deploy SSH key, clone repo, build, deploy Quadlet, install webhook
  • templates/docusaurus.container.j2 — nginx Quadlet
  • templates/docusaurus-rebuild.sh.j2 — idempotent rebuild script (used by playbook and webhook)
  • templates/hooks.json.j2 — webhook config with token validation
  • templates/docusaurus-webhook.service.j2 — systemd unit for the listener
  • defaults/main.yml — versions, ports, paths (override in inventory/host_vars/docusaurus/vars.yml)

Deploy from pve02:

cd ~/ansible
ansible-playbook playbooks/docusaurus.yml

Troubleshooting

Push doesn't trigger rebuild

ssh 10.69.20.77 'journalctl -u docusaurus-webhook --since "5 min ago"'
  • hook rule was not satisfied → secret mismatch between OneDev hook config and /etc/webhook/secret. Re-paste; no trailing whitespace.
  • No incoming request at all → check OneDev hook → recent deliveries. Verify URL reachable: curl -I http://10.69.20.77:9000/hooks/docusaurus-rebuild (expect 400 without payload).

Build fails

ssh 10.69.20.77 'journalctl -u docusaurus-webhook --since "10 min ago" | grep -E "(error|Error|FAIL)"'

Common: broken markdown links (with onBrokenLinks: 'throw'). Run npm run build locally to reproduce.

nginx serves stale content

ssh 10.69.20.77 'ls -la /srv/docusaurus/build/index.html' # check mtime
ssh 10.69.20.77 'systemctl restart docusaurus.service'

If mtime is recent but browser still old → hard refresh (Ctrl-Shift-R) or check Traefik cache (none configured by default).

Connection refused on git push to OneDev

ssh: connect to host onedev.home.helix9.org port 6611: Connection refused

DNS resolves OneDev hostname to Traefik IP, which only handles HTTPS (443) — not SSH (6611). Use the OneDev LXC IP directly:

git remote set-url origin ssh://git@10.69.20.76:6611/Docusaurus.git

Deploy SSH key broken

Re-generate by removing the keypair and re-running the playbook (it auto-regenerates):

ssh 10.69.20.77 'rm /root/.ssh/docusaurus_deploy*'
# from pve02
ansible-playbook playbooks/docusaurus.yml

The playbook prints the new public key. Add it to the OneDev account / project deploy keys.

Local client-side search via @easyops-cn/docusaurus-search-local. Index is built at compile time and bundled with the static output — no external service, queries never leave the browser.