README.md 19.5 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
---
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
sidebar-title: Dynamo Docs Guide
---

# How to Build and Publish Dynamo Docs

This document describes the architecture, workflows, and maintenance procedures for the
NVIDIA Dynamo documentation website powered by [Fern](https://buildwithfern.com).

<Note>
The documentation website is hosted entirely on
[Fern](https://buildwithfern.com). CI publishes to
`dynamo.docs.buildwithfern.com`; the production domain
`docs.dynamo.nvidia.com` is a custom domain alias that points to the
Fern-hosted site. There is no separate server — Fern handles hosting,
CDN, and versioned URL routing.
</Note>

<Error>
The `docs-website` branch is **CI-managed and must never be edited by
hand**. All documentation authoring happens on `main` (or a feature
branch based on `main`). The sync workflow copies changes to
`docs-website` automatically.
</Error>

---

## Table of Contents

- [Branch Architecture](#branch-architecture)
- [Directory Layout](#directory-layout)
- [Configuration Files](#configuration-files)
- [GitHub Workflows](#github-workflows)
  - [Fern Docs Workflow](#fern-docs-workflow-fern-docsyml)
  - [Docs Link Check Workflow](#docs-link-check-workflow-docs-link-checkyml)
- [Content Authoring](#content-authoring)
- [Callout Conversion](#callout-conversion)
- [Running Locally](#running-locally)
- [Version Management](#version-management)
- [How Publishing Works](#how-publishing-works)
- [Common Tasks](#common-tasks)
- [Claude Code Skills](#claude-code-skills)

---

## Claude Code Skills

Three Claude Code skills automate common docs tasks. Invoke them as slash
commands in Claude Code (e.g., `/add-dynamo-docs`) — each skill walks through
the full workflow: creating or editing the markdown file, updating the
navigation in `docs/versions/dev.yml`, and running `fern check` to validate.

| Skill | Description |
|-------|-------------|
| [add-dynamo-docs](/.claude/skills/add-dynamo-docs/SKILL.md) | Add a new page — creates the file with frontmatter, adds the nav entry |
| [rm-dynamo-docs](/.claude/skills/rm-dynamo-docs/SKILL.md) | Remove a page — deletes the file, removes the nav entry, checks for broken links |
| [update-dynamo-docs](/.claude/skills/update-dynamo-docs/SKILL.md) | Update a page — edit content, rename, or move between sections |

---

## Branch Architecture

The documentation system uses a **dual-branch model**:

| Branch | Purpose | Docs directory |
|---|---|---|
| `main` | Source of truth for **dev** (unreleased) documentation | `docs/` |
| `docs-website` | Published documentation including **all versioned snapshots** | `docs/` |

Authors edit pages on `main`. A GitHub Actions workflow automatically syncs
changes to the `docs-website` branch and publishes them to Fern. The
`docs-website` branch is never edited by hand — it is entirely managed by CI.

### Why two branches?

The `docs-website` branch accumulates versioned snapshots over time (e.g.
`pages-v0.8.0/`, `pages-v0.8.1/`). Keeping these on a separate branch avoids
bloating the `main` branch with frozen copies of old documentation.

---

## Directory Layout

### On `main`

```text
docs/
├── fern.config.json          # Fern org + CLI version pin
├── docs.yml                  # Site configuration (instances, branding, layout)
├── versions/
│   └── dev.yml               # Navigation tree for the dev version
├── pages/                    # Markdown content (the actual docs)
│   ├── getting-started/
│   ├── guides/
│   ├── kubernetes/
│   ├── reference/
│   └── ...
├── assets/                   # Images, fonts, SVGs, logos
├── components/
│   └── CustomFooter.tsx      # React component for the site footer
├── main.css                  # Custom CSS (NVIDIA branding, dark mode, etc.)
├── convert_callouts.py       # GitHub → Fern admonition converter script
└── diagrams/                 # D2 diagram source files
```

### On `docs-website`

The `docs-website` branch mirrors the above structure, plus versioned snapshots:

```text
docs/
├── docs.yml                  # Includes the full versions array
├── versions/
│   ├── dev.yml               # "Next" / dev navigation (synced from main)
│   ├── v0.8.1.yml            # Navigation for v0.8.1 snapshot
│   └── v0.8.0.yml            # Navigation for v0.8.0 snapshot
├── pages/                    # Current dev content (synced from main)
├── pages-v0.8.1/             # Frozen snapshot of pages/ at v0.8.1
├── pages-v0.8.0/             # Frozen snapshot of pages/ at v0.8.0
└── ...                       # (other files same as main)
```

Each `pages-vX.Y.Z/` directory is an immutable copy of `pages/` taken at
release time. The corresponding `versions/vX.Y.Z.yml` file is a copy of
`dev.yml` with all `../pages/` paths rewritten to `../pages-vX.Y.Z/`.

---

## Configuration Files

### `fern.config.json`

```json
{
  "organization": "nvidia",
  "version": "3.73.0"
}
```

- **organization**: The Fern organization that owns the docs site.
- **version**: Pins the Fern CLI version used for generation.

### `docs.yml`

This is the main Fern site configuration. Key sections:

| Section | Purpose |
|---|---|
| `instances` | Deployment targets — staging URL and custom production domain |
| `products` | Defines the product ("Dynamo") and its version list |
| `navbar-links` | GitHub repo link in the navigation bar |
| `footer` | Points to `CustomFooter.tsx` React component |
| `layout` | Page width, sidebar width, searchbar placement, etc. |
| `colors` | NVIDIA green (`#76B900`) accent, black/white backgrounds |
| `typography` | NVIDIA Sans body font, Roboto Mono code font |
| `logo` | NVIDIA logos (dark + light variants), 20px height |
| `js` | Adobe Analytics script injection |
| `css` | Custom `main.css` stylesheet |

**Important:** On `main`, `docs.yml` only lists the `dev` version. On
`docs-website`, it contains the **full versions array** (dev + all releases).
The sync workflow preserves the versions array from `docs-website` when copying
`docs.yml` from `main`.

### `versions/dev.yml`

Defines the navigation tree — the sidebar structure of the docs site. Each
entry maps a page title to a markdown file path:

```yaml
navigation:
  - section: Getting Started
    contents:
      - page: Quickstart
        path: ../pages/getting-started/quickstart.md
      - page: Support Matrix
        path: ../pages/reference/support-matrix.md
```

Sections can be nested. Pages can be marked as `hidden: true` to make them
accessible by URL but invisible in the sidebar.

---

## GitHub Workflows

### Fern Docs Workflow (`fern-docs.yml`)

**Location:** `.github/workflows/fern-docs.yml`

This single consolidated workflow handles linting, syncing, versioning, and
publishing. It runs three jobs depending on the trigger:

#### Job 1: Lint (PRs)

**Triggers:** Pull requests that modify `docs/**` files.

**Steps:**
1. `fern check` — validates Fern configuration syntax
2. `fern docs broken-links` — checks for broken internal links

**Purpose:** Catches broken docs before they merge.

#### Job 2: Sync dev (push to `main`)

**Triggers:** Push to `main` that modifies `docs/**` files, or manual
`workflow_dispatch` (with no tag specified).

**Steps:**
1. Checks out both `main` and `docs-website` branches side-by-side
2. Copies from `main``docs-website`:
   - `docs/pages/` — all markdown content
   - `docs/versions/dev.yml` — navigation structure
   - `docs/assets/` — images, fonts, SVGs
   - `docs/fern.config.json` — Fern config
   - `docs/components/` — React components
   - `docs/main.css` — custom styles
   - `docs/convert_callouts.py` — conversion script
3. Runs `convert_callouts.py` to transform GitHub-style callouts to Fern format
4. Updates `docs.yml` from `main` **while preserving the versions array** from
   `docs-website` (uses `yq` to save/restore the versions list)
5. Commits and pushes to `docs-website`
6. Publishes to Fern via `fern generate --docs`

**Symlink trick:** The Fern CLI expects a `fern/` directory. Since docs live in
`docs/`, the workflow creates a symlink `docs/fern → docs/.` (i.e., pointing to
itself) so Fern can find its config files.

#### Job 3: Version Release (tags)

**Triggers:** New Git tags matching `vX.Y.Z` (e.g., `v0.9.0`, `v1.0.0`), or
manual `workflow_dispatch` with a tag specified.

**Steps:**
1. Validates tag format (must be exactly `vX.Y.Z`, no suffixes like `-rc1`)
2. Checks that the version doesn't already exist (no duplicate snapshots)
3. Creates `docs/pages-vX.Y.Z/` by copying `docs/pages/`
4. Rewrites GitHub links in the snapshot:
   - `github.com/ai-dynamo/dynamo/tree/main``tree/vX.Y.Z`
   - `github.com/ai-dynamo/dynamo/blob/main``blob/vX.Y.Z`
5. Runs `convert_callouts.py` on the snapshot
6. Creates `docs/versions/vX.Y.Z.yml` from `dev.yml` with paths updated to
   `../pages-vX.Y.Z/`
7. Updates `docs.yml`:
   - Inserts new version right after the "dev" entry
   - Sets the product's default `path` to the new version
   - Updates the "Latest" display-name to `"Latest (vX.Y.Z)"`
8. Commits and pushes to `docs-website`
9. Publishes to Fern via `fern generate --docs`

**Anti-recursion note:** Pushes made with `GITHUB_TOKEN` do not trigger other
workflows (GitHub's built-in guard). This is why the publish step is inline in
each job rather than in a separate workflow.

### Docs Link Check Workflow (`docs-link-check.yml`)

**Location:** `.github/workflows/docs-link-check.yml`

**Triggers:** Push to `main` and pull requests.

Runs two independent link-checking jobs:

| Job | Tool | What it checks |
|---|---|---|
| `lychee` | [Lychee](https://lychee.cli.rs/) | External HTTP links (with caching, retries, rate-limit handling). Runs offline for PRs. |
| `broken-links-check` | Custom Python script (`detect_broken_links.py`) | Internal relative markdown links and symlinks. Creates GitHub annotations on PRs pointing to exact lines with broken links. |

---

## Content Authoring

### Writing docs on `main`

1. Edit or add markdown files in `docs/pages/`.
2. If adding a new page, add an entry in `docs/versions/dev.yml` to make it
   appear in the sidebar navigation.
3. Use standard GitHub-flavored markdown. Callouts (admonitions) should use
   GitHub's native syntax — they are automatically converted during sync:
   ```markdown
   > [!NOTE]
   > This is a note that will become a Fern `<Note>` component.

   > [!WARNING]
   > This warning will become a Fern `<Warning>` component.
   ```
4. Open a PR. The lint jobs (`fern check`, `fern docs broken-links`, lychee,
   broken-links-check) run automatically.
5. Once merged to `main`, the sync-dev workflow publishes changes within minutes.

### Assets and images

Place images in `docs/assets/` and reference them with relative paths from your
markdown files:

```markdown
![Architecture diagram](../assets/architecture.png)
```

### Custom components

React components in `docs/components/` can be used in markdown via MDX. The
`CustomFooter.tsx` renders the NVIDIA footer with legal links and branding.

---

## Callout Conversion

The `docs/convert_callouts.py` script bridges the gap between GitHub-flavored
markdown and Fern's admonition format. This lets authors use GitHub's native
callout syntax on `main` while Fern gets its required component format.

### Mapping

| GitHub Syntax | Fern Component |
|---|---|
| `> [!NOTE]` | `<Note>` |
| `> [!TIP]` | `<Tip>` |
| `> [!IMPORTANT]` | `<Info>` |
| `> [!WARNING]` | `<Warning>` |
| `> [!CAUTION]` | `<Error>` |

### Usage

```bash
# Convert all files in a directory (recursive, in-place)
python convert_callouts.py --dir docs/pages

# Convert a single file
python convert_callouts.py input.md output.md

# Run built-in tests
python convert_callouts.py --test
```

The conversion happens automatically during the sync-dev and release-version
workflows. Authors never need to run it manually.

---

## Running Locally

You can preview the documentation site on your machine using the
[Fern CLI](https://buildwithfern.com/learn/cli-api/overview). This is useful
for verifying layout, navigation, and content before opening a PR.

### Prerequisites

Install the Fern CLI globally via npm:

```bash
npm install -g fern-api
```

### Create the `fern` symlink

The Fern CLI requires its configuration files to live inside a directory called
`fern/`. In this repo the docs live in `docs/`, so you need to create a symlink
that points `fern` back to the same directory:

```bash
cd docs
ln -s . fern
```

This makes the CLI find `fern/fern.config.json`, `fern/docs.yml`, etc. without
moving any files. The symlink is listed in `.gitignore` and should not be
committed.

### Validate configuration

Run `fern check` to validate that `docs.yml`, `fern.config.json`, and the
navigation files are syntactically correct:

```bash
cd docs
fern check
```

### Check for broken links

Use `fern docs broken-links` to scan all pages for internal links that don't
resolve:

```bash
cd docs
fern docs broken-links
```

This is the same check that runs in CI on every pull request.

### Start a local preview server

Run `fern docs dev` to build the site and serve it locally with hot-reload:

```bash
cd docs
fern docs dev
```

The local server lets you see exactly how pages will look on the live site,
including navigation, version dropdowns, and custom styling.

---

## Version Management

### How versions work

The Fern site supports a version dropdown in the UI. Each version is defined by:

1. **A navigation file** (`docs/versions/vX.Y.Z.yml`) — sidebar structure
   pointing to version-specific pages.
2. **A pages directory** (`docs/pages-vX.Y.Z/`) — frozen snapshot of the
   markdown content at release time.
3. **An entry in `docs.yml`** — tells Fern about the version's display name,
   slug, and config path.

### Version types

| Version | Display Name | Slug | Description |
|---|---|---|---|
| Latest | `Latest (vX.Y.Z)` | `/` | Default version; points to the newest release |
| Stable releases | `vX.Y.Z` | `vX.Y.Z` | Immutable snapshots |
| Dev | `dev` | `dev` | Tracks `main`; updated on every push |

### URL structure

- **Latest (default):** `docs.dynamo.nvidia.com/dynamo/`
- **Specific version:** `docs.dynamo.nvidia.com/dynamo/v0.8.1/`
- **Dev:** `docs.dynamo.nvidia.com/dynamo/dev/`

### Creating a new version

Simply push a semver tag:

```bash
git tag v0.9.0
git push origin v0.9.0
```

The `release-version` job in `fern-docs.yml` handles everything else
automatically.

---

## How Publishing Works

```text
┌─────────────────────────────────────────────────────────────────────┐
│                        CONTINUOUS (dev)                             │
│                                                                     │
│  Developer pushes to main                                           │
│       │                                                             │
│       ▼                                                             │
│  docs/** changed? ── No ──▶ (nothing happens)                      │
│       │                                                             │
│      Yes                                                            │
│       │                                                             │
│       ▼                                                             │
│  sync-dev job:                                                      │
│    1. Copy docs/pages/, assets/, configs → docs-website branch      │
│    2. Convert GitHub callouts → Fern admonitions                    │
│    3. Preserve version list from docs-website's docs.yml            │
│    4. Commit + push to docs-website                                 │
│    5. fern generate --docs (publishes to Fern)                      │
│       │                                                             │
│       ▼                                                             │
│  Live on docs.dynamo.nvidia.com/dynamo/dev/ within minutes          │
└─────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────┐
│                      VERSION RELEASE                                │
│                                                                     │
│  Maintainer pushes vX.Y.Z tag                                       │
│       │                                                             │
│       ▼                                                             │
│  release-version job:                                               │
│    1. Validate tag format (vX.Y.Z only)                             │
│    2. Check version doesn't already exist                           │
│    3. Snapshot pages/ → pages-vX.Y.Z/                               │
│    4. Rewrite GitHub links (tree/main → tree/vX.Y.Z)               │
│    5. Convert callouts in snapshot                                  │
│    6. Create versions/vX.Y.Z.yml (paths → pages-vX.Y.Z/)          │
│    7. Update docs.yml (insert version, set as default)              │
│    8. Commit + push to docs-website                                 │
│    9. fern generate --docs (publishes to Fern)                      │
│       │                                                             │
│       ▼                                                             │
│  New version visible in dropdown at docs.dynamo.nvidia.com/dynamo/  │
└─────────────────────────────────────────────────────────────────────┘
```

### Secrets

| Secret | Purpose |
|---|---|
| `FERN_TOKEN` | Authentication token for `fern generate --docs`. Required for publishing. Stored in GitHub repo secrets. |

---

## Common Tasks

### Update existing documentation

1. Edit files in `docs/pages/` on a feature branch.
2. If adding a new page, add its entry in `docs/versions/dev.yml`.
3. Open a PR — linting runs automatically.
4. Merge — sync + publish happens automatically.

### Add a new top-level section

1. Create a directory under `docs/pages/` (e.g., `docs/pages/new-section/`).
2. Add markdown files for each page.
3. Add a new `- section:` block in `docs/versions/dev.yml` with the desired
   hierarchy.

### Release versioned documentation

```bash
git tag v1.0.0
git push origin v1.0.0
```

That's it. The workflow snapshots the current dev docs, creates the version
config, and publishes.

### Manually trigger a sync or release

Go to **Actions → Fern Docs → Run workflow**:
- Leave **tag** empty to trigger a dev sync.
- Enter a tag (e.g., `v0.9.0`) to trigger a version release.

### Debug a failed publish

1. Check the **Actions** tab for the failed `Fern Docs` workflow run.
2. Common issues:
   - **Broken links:** Fix the links flagged by `fern docs broken-links`.
   - **Invalid YAML:** Check `docs.yml` or `versions/dev.yml` syntax.
   - **Expired `FERN_TOKEN`:** Rotate the token in repo secrets.
   - **Duplicate version:** The tag was already released; check `docs-website`
     for existing `pages-vX.Y.Z/` directory.