README.md 19.8 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
---
# 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

50
51
52
53
A single Claude Code skill automates common docs tasks. Invoke it as a slash
command in Claude Code (e.g., `/dynamo-docs`) — the skill walks through
the full workflow: creating, editing, or removing the markdown file, updating
the navigation in `docs/index.yml`, and running `fern check` to validate.
54
55
56

| Skill | Description |
|-------|-------------|
57
| [dynamo-docs](https://github.com/ai-dynamo/dynamo/blob/main/.claude/skills/dynamo-docs/SKILL.md) | Add, update, move, or remove a docs page |
58
59
60
61
62
63
64

---

## Branch Architecture

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

65
66
67
68
| Branch | Purpose | Content | Fern config |
|---|---|---|---|
| `main` | Source of truth for **dev** (unreleased) documentation | `docs/` | `fern/` |
| `docs-website` | Published documentation including **all versioned snapshots** | `fern/pages/` | `fern/` |
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86

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
87
88
89
fern/                             # Fern CLI configuration (fern/ is a Fern convention)
├── fern.config.json              # Fern org + CLI version pin
├── docs.yml                      # Site configuration (instances, branding, layout)
90
├── components/
91
92
93
94
95
96
97
98
99
100
101
102
103
104
│   └── 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
└── .gitignore                    # Fern-specific ignores

docs/                             # Documentation content
├── index.yml                     # Navigation tree for the dev version
├── getting-started/              # Markdown content (the actual docs)
├── kubernetes/
├── reference/
├── ...
├── assets/                       # Images, fonts, SVGs, logos
├── blogs/                        # Blog posts
└── diagrams/                     # D2 diagram source files
105
106
107
108
```

### On `docs-website`

109
110
The `docs-website` branch has a different layout optimized for Fern's directory
conventions, plus versioned snapshots:
111
112

```text
113
114
115
fern/
├── fern.config.json              # Fern org + CLI version pin
├── docs.yml                      # Includes the full versions array
116
├── versions/
117
118
119
120
121
122
123
124
125
126
127
│   ├── 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
├── components/                   # React components
├── main.css                      # Custom CSS
├── convert_callouts.py           # Callout converter
├── blogs/                        # Blog posts (synced from main)
└── assets/                       # Images, fonts, SVGs
128
129
130
131
132
133
```

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/`.

134
135
136
The sync workflow copies content from `main`'s `docs/` into `fern/pages/` and
transforms navigation paths in `index.yml``versions/dev.yml` accordingly.

137
138
139
140
---

## Configuration Files

141
### `fern/fern.config.json`
142
143
144
145
146
147
148
149
150
151
152

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

153
### `fern/docs.yml`
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174

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

175
### `docs/index.yml`
176
177
178
179
180
181
182
183
184

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
185
        path: getting-started/quickstart.md
186
      - page: Support Matrix
187
        path: reference/support-matrix.md
188
189
```

190
191
192
193
194
195
196
Paths are relative to the `docs/` directory. Sections can be nested. Pages can
be marked as `hidden: true` to make them accessible by URL but invisible in the
sidebar.

During sync to `docs-website`, the workflow copies `index.yml` to
`fern/versions/dev.yml` and transforms paths (e.g., `getting-started/X`
`../pages/getting-started/X`) to match the docs-website directory layout.
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

---

## 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
226
227
228
229
230
231
232
233
2. Copies content from `main`'s `docs/``docs-website`'s `fern/pages/`
3. Copies `docs/index.yml``fern/versions/dev.yml` and transforms paths
   for the docs-website layout using `yq`
4. Syncs assets from `docs/assets/` and blogs from `docs/blogs/`
5. Copies Fern config files from `fern/` → docs-website's `fern/`
   (`fern.config.json`, `components/`, `main.css`, `convert_callouts.py`)
6. Runs `convert_callouts.py` to transform GitHub-style callouts to Fern format
7. Updates `docs.yml` from `main` **while preserving the versions array** from
234
   `docs-website` (uses `yq` to save/restore the versions list)
235
236
8. Commits and pushes to `docs-website`
9. Publishes to Fern via `fern generate --docs`
237
238
239
240
241
242
243
244
245

#### 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)
246
3. Creates `fern/pages-vX.Y.Z/` by copying `fern/pages/`
247
248
249
250
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
251
6. Creates `fern/versions/vX.Y.Z.yml` from `dev.yml` with paths updated to
252
   `../pages-vX.Y.Z/`
253
7. Updates `fern/docs.yml`:
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
   - 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`

283
284
285
1. Edit or add markdown files in `docs/`.
2. If adding a new page, add an entry in `docs/index.yml` to make it appear
   in the sidebar navigation.
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
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

310
React components in `fern/components/` can be used in markdown via MDX. The
311
312
313
314
315
316
`CustomFooter.tsx` renders the NVIDIA footer with legal links and branding.

---

## Callout Conversion

317
The `fern/convert_callouts.py` script bridges the gap between GitHub-flavored
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
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)
335
python fern/convert_callouts.py --dir docs/
336
337

# Convert a single file
338
python fern/convert_callouts.py input.md output.md
339
340

# Run built-in tests
341
python fern/convert_callouts.py --test
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
```

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
```

### Validate configuration

365
366
Run `fern check` from the repo root to validate that `fern/docs.yml`,
`fern/fern.config.json`, and the navigation files are syntactically correct:
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

```bash
fern check
```

### Check for broken links

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

```bash
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
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:

402
403
404
405
406
407
1. **A navigation file** (`fern/versions/vX.Y.Z.yml`) — sidebar structure
   pointing to version-specific pages (on the `docs-website` branch).
2. **A pages directory** (`fern/pages-vX.Y.Z/`) — frozen snapshot of the
   markdown content at release time (on the `docs-website` branch).
3. **An entry in `fern/docs.yml`** — tells Fern about the version's display
   name, slug, and config path.
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

### 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:                                                      │
452
453
454
455
456
457
│    1. Copy docs/ content → fern/pages/ on docs-website branch │
│    2. Copy fern/ configs → fern/ on docs-website branch             │
│    3. Convert GitHub callouts → Fern admonitions                    │
│    4. Preserve version list from docs-website's docs.yml            │
│    5. Commit + push to docs-website                                 │
│    6. fern generate --docs (publishes to Fern)                      │
458
459
460
461
462
463
464
465
466
467
468
469
470
471
│       │                                                             │
│       ▼                                                             │
│  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                           │
472
│    3. Snapshot fern/pages/ → fern/pages-vX.Y.Z/                     │
473
474
│    4. Rewrite GitHub links (tree/main → tree/vX.Y.Z)               │
│    5. Convert callouts in snapshot                                  │
475
476
│    6. Create fern/versions/vX.Y.Z.yml (paths → pages-vX.Y.Z/)     │
│    7. Update fern/docs.yml (insert version, set as default)         │
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
│    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

497
498
1. Edit files in `docs/` on a feature branch.
2. If adding a new page, add its entry in `docs/index.yml`.
499
500
501
502
503
3. Open a PR — linting runs automatically.
4. Merge — sync + publish happens automatically.

### Add a new top-level section

504
1. Create a directory under `docs/` (e.g., `docs/new-section/`).
505
2. Add markdown files for each page.
506
3. Add a new `- section:` block in `docs/index.yml` with the desired hierarchy.
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528

### 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`.
529
   - **Invalid YAML:** Check `fern/docs.yml` or `docs/index.yml` syntax.
530
531
   - **Expired `FERN_TOKEN`:** Rotate the token in repo secrets.
   - **Duplicate version:** The tag was already released; check `docs-website`
532
     for existing `fern/pages-vX.Y.Z/` directory.