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.
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.
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.
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.
maindocs-websiteThe docs-website branch has a different layout optimized for Fern’s directory
conventions, 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/.
The sync workflow copies content from main’s docs/ into fern/pages/ and
transforms navigation paths in index.yml → versions/dev.yml accordingly.
fern/fern.config.jsonfern/docs.ymlThis 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.
docs/index.ymlDefines the navigation tree — the sidebar structure of the docs site. Each entry maps a page title to a markdown file path:
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.
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:
Triggers: Pull requests that modify docs/** files.
Steps:
fern check — validates Fern configuration syntaxfern docs broken-links — checks for broken internal linksPurpose: Catches broken docs before they merge.
main)Triggers: Push to main that modifies docs/** files, or manual
workflow_dispatch (with no tag specified).
Steps:
main and docs-website branches side-by-sidemain’s docs/ → docs-website’s fern/pages/docs/index.yml → fern/versions/dev.yml and transforms paths
for the docs-website layout using yqdocs/assets/ and blogs from docs/blogs/fern/ → docs-website’s fern/
(fern.config.json, components/, main.css, convert_callouts.py)convert_callouts.py to transform GitHub-style callouts to Fern formatdocs.yml from main while preserving the versions array from
docs-website (uses yq to save/restore the versions list)docs-websitefern generate --docsTriggers: New Git tags matching vX.Y.Z (e.g., v0.9.0, v1.0.0), or
manual workflow_dispatch with a tag specified.
Steps:
vX.Y.Z, no suffixes like -rc1)fern/pages-vX.Y.Z/ by copying fern/pages/github.com/ai-dynamo/dynamo/tree/v1.0.1 → tree/vX.Y.Zgithub.com/ai-dynamo/dynamo/blob/v1.0.1 → blob/vX.Y.Zconvert_callouts.py on the snapshotfern/versions/vX.Y.Z.yml from dev.yml with paths updated to
../pages-vX.Y.Z/fern/docs.yml:
path to the new version"Latest (vX.Y.Z)"docs-websitefern generate --docsAnti-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.yml)Location: .github/workflows/docs-link-check.yml
Triggers: Push to main and pull requests.
Runs two independent link-checking jobs:
maindocs/.docs/index.yml to make it appear
in the sidebar navigation.fern check, fern docs broken-links, lychee,
broken-links-check) run automatically.main, the sync-dev workflow publishes changes within minutes.Place images in docs/assets/ and reference them with relative paths from your
markdown files:
React components in fern/components/ can be used in markdown via MDX. The
CustomFooter.tsx renders the NVIDIA footer with legal links and branding.
The fern/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.
The conversion happens automatically during the sync-dev and release-version workflows. Authors never need to run it manually.
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.
Install the Fern CLI globally via npm:
Run fern check from the repo root to validate that fern/docs.yml,
fern/fern.config.json, and the navigation files are syntactically correct:
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.
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.
The Fern site supports a version dropdown in the UI. Each version is defined by:
fern/versions/vX.Y.Z.yml) — sidebar structure
pointing to version-specific pages (on the docs-website branch).fern/pages-vX.Y.Z/) — frozen snapshot of the
markdown content at release time (on the docs-website branch).fern/docs.yml — tells Fern about the version’s display
name, slug, and config path.docs.dynamo.nvidia.com/dynamo/docs.dynamo.nvidia.com/dynamo/v0.8.1/docs.dynamo.nvidia.com/dynamo/dev/Simply push a semver tag:
The release-version job in fern-docs.yml handles everything else
automatically.
docs/ on a feature branch.docs/index.yml.docs/ (e.g., docs/new-section/).- section: block in docs/index.yml with the desired hierarchy.That’s it. The workflow snapshots the current dev docs, creates the version config, and publishes.
Go to Actions → Fern Docs → Run workflow:
v0.9.0) to trigger a version release.Fern Docs workflow run.fern docs broken-links.fern/docs.yml or docs/index.yml syntax.FERN_TOKEN: Rotate the token in repo secrets.docs-website
for existing fern/pages-vX.Y.Z/ directory.