📄

Developer Guide — Docs-Site

How to write and publish documentation as a developer on the Registry Platform

Sign in to see your personalized configuration examples Sign In

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

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 entire docs/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 title and description for page metadata. If a canonical file is missing these fields, docs-sync.py writes 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 title for the <title> tag and description for the meta description.

Auto-derived values (when ensure_fields kicks in):

FieldAuto-derived value
titleFilename stem, title-cased: quick-startQuick Start
descriptionEmpty 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

  1. Create the canonical file in the right location (see Where Does My Doc Go?):

    touch docs/docs-site/guides/ci-integration.md
  2. Write the content with required frontmatter.

  3. Run sync:

    uv run scripts/docs-sync.py sync
  4. 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.

  5. 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.