Developer Guide — Docs-Site
How to write and publish documentation as a developer on the Registry Platform
Developer Guide — Docs-Site
This guide covers the day-to-day workflow for software developers contributing documentation to the Registry Platform.
Table of Contents
- Day-to-Day Workflow
- Frontmatter
- Using docs-sync.py
- Understanding .service-specific/
- Testing Docs Locally
- Adding New Pages
- Where Does My Doc Go?
- Linking Between Pages
- Mermaid Diagrams in VitePress
- Writing Tips
Day-to-Day Workflow
The core loop is: edit canonical file → sync → commit.
flowchart LR
edit["1. Edit\ndocs/docs-site/…/file.md"]
sync["2. Sync\ntask sync"]
verify["3. Verify\ntask docs:check"]
commit["4. Commit\ngit add docs/docs-site/\ngit commit"]
edit --> sync --> verify --> commit
# 1. Edit your canonical Markdown file
$EDITOR docs/docs-site/guides/authentication.md
# 2. Regenerate the service-specific content roots
uv run scripts/docs-sync.py sync
# 3. Verify nothing is broken
uv run scripts/docs-sync.py check
# 4. Stage and commit everything (canonical + symlinks)
git add docs/docs-site/
git commit -m "docs: update authentication guide"
Do not run
git add docs/docs-site/.service-specific/separately. Always stage the entiredocs/docs-site/directory so canonical files and generated symlinks stay in sync in the same commit.
Frontmatter
Every canonical .md file should have at least title and description in its
YAML frontmatter block:
---
title: Authentication Guide
description: How to authenticate with the Registry Platform using tokens and certificates
---
# Authentication Guide
...
Why this matters:
- The Next.js site requires
titleanddescriptionfor page metadata. If a canonical file is missing these fields,docs-sync.pywrites a real file with auto-derived values rather than a symlink. Adding them to the canonical file restores the symlink on next sync, keeping the repository clean. - VitePress uses
titlefor the<title>tag anddescriptionfor the meta description.
Auto-derived values (when ensure_fields kicks in):
| Field | Auto-derived value |
|---|---|
title | Filename stem, title-cased: quick-start → Quick Start |
description | Empty string "" |
Using docs-sync.py
The script lives at scripts/docs-sync.py and uses uv
for dependency management — no virtualenv setup required.
sync — Apply changes
# Sync all services (most common)
uv run scripts/docs-sync.py sync
# Preview what would change, without writing anything
uv run scripts/docs-sync.py sync --dry-run
# Sync only one service
uv run scripts/docs-sync.py sync --service vitepress
uv run scripts/docs-sync.py sync --service nextjs
Example output after adding a new file:
╭────────────────────────────────────────────────╮
│ docs-sync — root: /opt/services/registries/... │
╰────────────────────────────────────────────────╯
▶ vitepress → .service-specific/vitepress
+ 🔗 .service-specific/vitepress/guides/new-guide.md
created=1 updated=0 deleted=0 skipped=22 errors=0
▶ nextjs → .service-specific/nextjs
+ 🔗 .service-specific/nextjs/guides/new-guide.mdx
created=1 updated=0 deleted=0 skipped=8 errors=0
Icons: 🔗 = symlink created (no frontmatter changes needed), 📄 = real file written
(frontmatter was injected or transformed).
status — Inspect current state
# Show status table for all services
uv run scripts/docs-sync.py status
# Show status for one service
uv run scripts/docs-sync.py status --service nextjs
The status table shows each canonical file and whether its service-specific output is in sync, missing, stale, or should be a symlink instead of a real file.
check — Validate symlinks
uv run scripts/docs-sync.py check
# ✓ All symlinks are valid.
Returns exit code 0 if all symlinks resolve, exit code 1 with details if any are broken.
Use this after a sync to confirm nothing went wrong.
Understanding .service-specific/
The .service-specific/ directory is fully generated by docs-sync.py. You
should understand it, but you should never edit it by hand.
docs/docs-site/.service-specific/
├── vitepress/ ← VitePress reads this as its srcDir
│ ├── index.md ────────────────→ ../../index.md
│ ├── guides/
│ │ └── quickstart.md ───────→ ../../../guides/quickstart.md
│ ├── docker/
│ │ └── index.md ────────────→ ../../../docker/index.md
│ └── …
└── nextjs/ ← Next.js reads this as CONTENT_DIR
└── guides/
├── quickstart.mdx ───────→ ../../../guides/quickstart.md
└── …
Why symlinks? Symlinks mean zero content duplication. The canonical .md file
is the single source of truth. VitePress and Next.js simply see the file at the
expected path and with the expected extension.
Why relative symlinks? The same symlink tree works whether the directory is at
/opt/services/registries/docs/docs-site/ on the host or /app/docs/ inside a
container — because the symlinks are relative to their own location, not absolute paths.
Testing Docs Locally
The docs are served by two containers. If they are running, you can verify your changes immediately after sync:
# Bring up the docs services
task docs:up
# Check VitePress (full technical reference)
curl -s http://localhost:5173/ | head -5
# Check Next.js (guided experience — guides/ only)
curl -s http://localhost:3000/ | head -5
For a content-only change (no frontmatter transforms changed), the sync output is
immediately visible once the VitePress container rebuilds. The VitePress container
runs vitepress build at startup, so you need to restart it to pick up new builds:
# Restart VitePress after a content change
podman-compose -f compose/docs.vitepress.yml restart registry-docs-vitepress
# Or simply bring the whole stack down and up
task docs:down && task docs:up
The Next.js container serves content dynamically and does not need a restart for
content changes — it reads .service-specific/nextjs/ at request time.
Adding New Pages
-
Create the canonical file in the right location (see Where Does My Doc Go?):
touch docs/docs-site/guides/ci-integration.md -
Write the content with required frontmatter.
-
Run sync:
uv run scripts/docs-sync.py sync -
For VitePress — if you want the page to appear in the sidebar, add it to the sidebar config in
docs-site-vitepress/.vitepress/config.mts. VitePress will render the page even without a sidebar entry, but it won't be discoverable via navigation. -
Commit the canonical file and the generated symlinks together.
Where Does My Doc Go?
docs/docs-site/
├── guides/ ← cross-cutting content that appears in BOTH sites
│ └── my-guide.md ← visible in VitePress + Next.js
│
├── docker/ ← Docker Registry-specific docs (VitePress only)
├── maven/ ← Maven (Reposilite)-specific docs (VitePress only)
├── npm/ ← npm (Verdaccio)-specific docs (VitePress only)
├── pypi/ ← PyPI (DevPI)-specific docs (VitePress only)
│
└── features/ ← docs about the docs-site system itself (VitePress only)
Rule of thumb:
- If the content is relevant to developers working with any registry (authentication,
quickstart, troubleshooting), put it in
guides/. - If it is specific to one registry type (Docker push commands, Maven settings.xml), put it in the registry-specific directory.
- If it documents the docs-site system itself, put it in
features/.
Linking Between Pages
VitePress links
VitePress resolves links relative to the srcDir (.service-specific/vitepress/).
Use root-relative paths starting with /:
<!-- From any page, link to the quickstart guide -->
[Quickstart](/guides/quickstart)
<!-- Link to the Docker developer guide -->
[Docker Developer Guide](/docker/developer)
Or use relative paths from the current file's location:
<!-- From guides/authentication.md, link to guides/quickstart.md -->
[Quickstart](quickstart)
<!-- From docker/developer.md, link to guides/authentication.md -->
[Authentication](../guides/authentication)
Next.js links
Next.js only includes guides/ content. Links within guides use relative paths:
<!-- From guides/authentication.md, link to guides/quickstart.md -->
[Quickstart](quickstart)
Avoid hard-coding .md or .mdx extensions in links — VitePress strips extensions
when resolving links, and Next.js uses its own routing.
Mermaid Diagrams in VitePress
VitePress has built-in Mermaid support. Use fenced code blocks with the mermaid language:
```mermaid
flowchart LR
A["Step 1"] --> B["Step 2"] --> C["Step 3"]
```
Common diagram types used in this documentation:
flowchart LR
flowchart["flowchart\nprocess flows"] --> seq["sequenceDiagram\nservice interactions"]
seq --> graph["graph TD\ndecision trees"]
Note: Mermaid diagrams render in VitePress only. If a file is also served by
Next.js (i.e., it lives in guides/), ensure the surrounding prose still makes
sense without the diagram — the Next.js MDX renderer may not support Mermaid
out of the box.
Writing Tips
Be specific about commands. Always show the full command including the path, so readers can copy-paste without guessing the working directory:
# Good
uv run scripts/docs-sync.py sync
# Avoid
run the sync script
Use code blocks for every command, path, and config snippet. Inline code
is appropriate for short identifiers; fenced blocks for anything runnable.
Structure with ## sections, not walls of text. Developers skim — make each
section self-contained so they can jump directly to what they need.
Document the why, not just the what. If there's a non-obvious constraint (e.g., "symlinks use relative paths so the tree works inside containers"), explain it.
Keep canonical frontmatter complete. The more complete the canonical frontmatter, the more services can use symlinks rather than real files — keeping the repo cleaner.