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.
The documentation website is hosted entirely on
Fern. 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.
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.
Table of Contents
- Branch Architecture
- Directory Layout
- Configuration Files
- GitHub Workflows
- Content Authoring
- Callout Conversion
- Running Locally
- Version Management
- How Publishing Works
- Common Tasks
- 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.
Branch Architecture
The documentation system uses a dual-branch model:
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
On docs-website
The docs-website branch mirrors the above structure, plus versioned snapshots:
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
- 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:
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:
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:
fern check— validates Fern configuration syntaxfern 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:
- Checks out both
mainanddocs-websitebranches side-by-side - Copies from
main→docs-website:docs/pages/— all markdown contentdocs/versions/dev.yml— navigation structuredocs/assets/— images, fonts, SVGsdocs/fern.config.json— Fern configdocs/components/— React componentsdocs/main.css— custom stylesdocs/convert_callouts.py— conversion script
- Runs
convert_callouts.pyto transform GitHub-style callouts to Fern format - Updates
docs.ymlfrommainwhile preserving the versions array fromdocs-website(usesyqto save/restore the versions list) - Commits and pushes to
docs-website - 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:
- Validates tag format (must be exactly
vX.Y.Z, no suffixes like-rc1) - Checks that the version doesn’t already exist (no duplicate snapshots)
- Creates
docs/pages-vX.Y.Z/by copyingdocs/pages/ - Rewrites GitHub links in the snapshot:
github.com/ai-dynamo/dynamo/tree/main→tree/vX.Y.Zgithub.com/ai-dynamo/dynamo/blob/main→blob/vX.Y.Z
- Runs
convert_callouts.pyon the snapshot - Creates
docs/versions/vX.Y.Z.ymlfromdev.ymlwith paths updated to../pages-vX.Y.Z/ - Updates
docs.yml:- Inserts new version right after the “dev” entry
- Sets the product’s default
pathto the new version - Updates the “Latest” display-name to
"Latest (vX.Y.Z)"
- Commits and pushes to
docs-website - 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:
Content Authoring
Writing docs on main
- Edit or add markdown files in
docs/pages/. - If adding a new page, add an entry in
docs/versions/dev.ymlto make it appear in the sidebar navigation. - Use standard GitHub-flavored markdown. Callouts (admonitions) should use
GitHub’s native syntax — they are automatically converted during sync:
- Open a PR. The lint jobs (
fern check,fern docs broken-links, lychee, broken-links-check) run automatically. - 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:
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
Usage
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. This is useful for verifying layout, navigation, and content before opening a PR.
Prerequisites
Install the Fern CLI globally via npm:
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:
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:
Check for broken links
Use fern docs broken-links to scan all pages for internal links that don’t
resolve:
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:
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:
- A navigation file (
docs/versions/vX.Y.Z.yml) — sidebar structure pointing to version-specific pages. - A pages directory (
docs/pages-vX.Y.Z/) — frozen snapshot of the markdown content at release time. - An entry in
docs.yml— tells Fern about the version’s display name, slug, and config path.
Version types
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:
The release-version job in fern-docs.yml handles everything else
automatically.
How Publishing Works
Secrets
Common Tasks
Update existing documentation
- Edit files in
docs/pages/on a feature branch. - If adding a new page, add its entry in
docs/versions/dev.yml. - Open a PR — linting runs automatically.
- Merge — sync + publish happens automatically.
Add a new top-level section
- Create a directory under
docs/pages/(e.g.,docs/pages/new-section/). - Add markdown files for each page.
- Add a new
- section:block indocs/versions/dev.ymlwith the desired hierarchy.
Release versioned documentation
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
- Check the Actions tab for the failed
Fern Docsworkflow run. - Common issues:
- Broken links: Fix the links flagged by
fern docs broken-links. - Invalid YAML: Check
docs.ymlorversions/dev.ymlsyntax. - Expired
FERN_TOKEN: Rotate the token in repo secrets. - Duplicate version: The tag was already released; check
docs-websitefor existingpages-vX.Y.Z/directory.
- Broken links: Fix the links flagged by