| name | vcluster-docs-writer |
| description | Write and edit vCluster Docusaurus documentation. Use this skill when working with .mdx or .md files in the vcluster-docs repository. Handles vale linting, partials discovery, link validation, versioned docs, and release processes. |
| context | fork |
vCluster Documentation Writer
Specialized knowledge and workflows for writing vCluster documentation in Docusaurus.
Dependencies
When this skill is loaded, also invoke: vcluster-brand:vcluster-brand
When to Use
Trigger when:
- Working in
/home/decoder/loft/vcluster-docs repository
- User asks to write, edit, review, or fix documentation
- User mentions: vale, partials, doc links, versioned docs, releases
- User needs to discover partials, components, or code blocks
- User asks about vCluster terminology or style guidelines
Quick Workflows
Writing New Documentation
- Review style guidelines in
references/style-guide.md
- Follow document template structure (Overview → How to use → Examples → Limitations)
- Discover available resources:
scripts/discover_partials.sh
scripts/detect_language.sh <file>
scripts/generate_import.sh <path>
- Run vale for style checking:
scripts/run_vale.sh <file-path>
- Verify links before committing (see Link Validation below)
See references/style-guide.md for complete writing guidelines.
Editing Existing Documentation
- Check if file is versioned (
vcluster_versioned_docs/version-*/)
- ⚠️ CRITICAL: NEVER modify versioned docs unless explicitly requested
- Only update files in main
vcluster/ folder for current docs
- Run vale before and after edits:
scripts/run_vale.sh <file-path>
- Verify links if modified (see Link Validation below)
Link Validation and Fixing
Core principle: Use relative file paths with .mdx extensions, NOT URL paths.
❌ WRONG (URL paths):
[Pod Identity](/vcluster/integrations/pod-identity/eks-pod-identity)
✅ CORRECT (file paths with .mdx):
[Pod Identity](../../../../third-party-integrations/pod-identity/eks-pod-identity.mdx)
Debugging broken links efficiently:
cd vcluster_versioned_docs/version-X.X.X/path/from/error/
grep -r "filename-from-error" . --include="*.mdx"
See references/link-formatting.md for detailed examples and rules.
Release Preparation
⚠️ CRITICAL before cutting new versioned docs release:
- Check for problematic patterns:
grep -r "/vcluster/integrations\|/vcluster/deploy/security" vcluster_versioned_docs/version-X.X.X/
- Fix absolute URL paths → relative file paths (common files listed in reference)
- Clear caches before building:
rm -rf .docusaurus build node_modules/.cache
- NEVER run
npm run build - User runs when needed
See references/release-checklist.md for complete release workflow.
Versioned Docs and lastVersion Routing
Critical concept: When lastVersion changes in docusaurus.config.js, URL routing changes.
lastVersion: "0.30.0" → /docs/vcluster/... routes to 0.30.0 content
/docs/vcluster/0.29.0/... routes to 0.29.0 content
Key insight: /docs/vcluster/... (no version) ALWAYS routes to lastVersion content.
Why links break:
- Older versioned docs using unversioned paths resolve to new lastVersion
- Cross-plugin links (vCluster→Platform or Platform→vCluster) resolve to each plugin's lastVersion
- Path restructuring between versions causes 404s
Version isolation principle: Each version should link within itself:
<!-- In 0.29.0 docs - CORRECT -->
[Link](../configure/something.mdx) <!-- relative file path -->
<!-- In 0.29.0 docs - RISKY (resolves to lastVersion) -->
[Link](/docs/vcluster/configure/something) <!-- unversioned URL -->
Cross-plugin links (vCluster→Platform): Always resolve to Platform's lastVersion:
<!-- These will break when Platform restructures paths -->
[Platform docs](/docs/platform/administer/monitoring/...)
<!-- After Platform 4.6.0, this became: -->
[Platform docs](/docs/platform/maintenance/monitoring/...)
Fixing broken links after version release:
grep -r "/docs/platform/administer/\|/platform/api/" vcluster vcluster_versioned_docs --include="*.mdx"
find vcluster vcluster_versioned_docs -name "*.mdx" \
-exec sed -i 's|/platform/old/path|/platform/new/path|g' {} \;
Sidebar Navigation Conventions
Docusaurus sidebar categories can be made clickable (navigating to a page on click) in two ways. Both are banned — sidebar section labels must expand/collapse only.
Anti-pattern 1: link field in _category_.json
{
"label": "Deploy",
"position": 2,
"link": {
"type": "generated-index"
}
}
Remove the link field entirely. The label and position fields are sufficient.
Anti-pattern 2: README.mdx as a folder index (general docs)
Docusaurus automatically converts a README.mdx file into the category's landing page and makes the section label clickable — even without a link in _category_.json. Do not create README.mdx files as folder indices in general docs sections.
Instead, use overview.mdx with an explicit slug: that matches the directory URL:
---
title: Networking
sidebar_label: Overview
sidebar_position: 0
slug: /configure/vcluster-yaml/networking
---
When converting an existing README.mdx to overview.mdx:
- Add
slug: matching the directory's URL (plugin-relative, no /vcluster/ prefix).
- Set
sidebar_label: Overview — not the section name, to avoid duplication.
- Migrate
sidebar_position and sidebar_class_name out of the MDX frontmatter and into _category_.json as position and className respectively. The _category_.json controls the section label in the sidebar; the overview.mdx frontmatter controls only the child item.
- Update any relative links inside the file that previously resolved from a directory URL — they now resolve from the slug URL.
_category_.json structure for a folder that has an overview.mdx:
{
"label": "Networking",
"position": 3,
"className": "host-nodes private-nodes"
}
Do not add a link field.
Exception: vcluster/configure/vcluster-yaml/ uses README.mdx
The vcluster-yaml section mirrors the structure of the vcluster.yaml config file — every folder is a yaml key. Labeling these pages "Overview" is semantically wrong because there is no overview key in the yaml. In this section only, use README.mdx (not overview.mdx) as the folder index. Docusaurus treats it as the category header click target; it does not appear as a named sidebar item. Do NOT set sidebar_label: Overview or sidebar_position: 0 on these files — they are irrelevant once the file becomes the category index.
PR Preview URLs
Preview links MUST point to actual changed pages, NOT the docs root.
Base: https://deploy-preview-{PR}--vcluster-docs-site.netlify.app/docs
Derive the path from the file location — version goes after product name:
docs/platform/install/x.mdx → /docs/platform/next/install/x
docs/vcluster/deploy/x.mdx → /docs/vcluster/next/deploy/x
versioned_docs/version-0.31/vcluster/deploy/x.mdx → /docs/vcluster/0.31/deploy/x
versioned_docs/version-0.28/platform/api/x.mdx → /docs/platform/0.28/api/x
The version marked as lastVersion in docusaurus.config.ts has NO version segment in the URL. For example, if platform lastVersion is 4.6.0, then versioned_docs/version-4.6.0/platform/install/x.mdx → /docs/platform/install/x.
List a preview link for EVERY modified document in the PR body's Preview section.
Backporting Assessment
New work typically lands in docs/ (the next version). Before completing a PR, assess whether changes should also apply to released versions in versioned_docs/:
- Backport: Factual errors, broken links, typos — apply to relevant prior versions
- Don't backport: New feature documentation — only applies to
next
- When unclear: Note in the PR description that backporting should be reviewed
When backporting: make the same changes in relevant versioned_docs/version-X.XX/ folders and include preview links for those versions too.
Integration Tests (BrowserStack)
Cross-browser tests for documentation rendering (mermaid diagrams, and so on).
Location: tests/ directory
Structure:
tests/
├── specs/ # Test files go here
│ └── mermaid-rendering.spec.js
├── browserstack.yml # BrowserStack SDK config
├── playwright.config.js # Playwright config
└── package.json
Creating new tests:
- Create spec file in
tests/specs/ with .spec.js extension
- Use Playwright Test format:
const { test, expect } = require('@playwright/test');
test.describe('Feature Name', () => {
test('description', async ({ page }) => {
await page.goto(process.env.TEST_URL || 'https://www.vcluster.com/docs/...');
});
});
- Tests run on 7 browser configs (Safari/Chrome/Firefox on macOS/Windows)
Running tests:
cd tests
npm run test:local
npm run test:browserstack
CI: Tests run automatically on PRs using .github/workflows/integration-tests.yml
Partials and Components
Quick commands:
scripts/discover_partials.sh
scripts/detect_language.sh file.yml
scripts/generate_import.sh <path> <dir>
Import patterns:
import Code from '!!raw-loader!@site/docs/_code/example.yaml';
<CodeBlock language="yaml">{Code}</CodeBlock>
import Partial from '@site/docs/_partials/example.mdx';
<Partial />
See references/partials-guide.md for complete patterns and troubleshooting.
Concept and Explanation Pages
Explanation pages (architecture, overview, "what is X") should build the reader's mental model from the outside in. Each section should answer the question a reader would naturally ask next, given what they just learned.
The progressive disclosure sequence:
- What it IS — plain-language definition before any technical framing
- What it contains — structural components and their roles
- How the parts connect — topology, agents, registrations
- How you interact with it — entry points, access patterns
- How work flows through it — lifecycle, reconciliation, request paths
- Operational detail — network paths, failure behavior, edge cases
Rules:
- Do not introduce a term before the concept behind it is established. Glossary links help but do not substitute for a clear conceptual foundation.
- Add a plain-language "why this matters" sentence before technical component lists. Readers need to know what the list is for before they can absorb its items.
- Place diagrams immediately after the content they illustrate — not after examples that build on the pattern. A diagram should reinforce what was just described, not summarize what follows.
- Do not open explanation pages with defensive disclaimers ("X does not replace Y"). Put the relationship between products where it naturally belongs in the flow — usually at the handoff point between sections.
- Merge sections with confusingly similar names. Adjacent sections covering the same concept from slightly different angles (for example, "Project lifecycle" and "Project resource lifecycle") signal a structural problem, not a content problem.
- Separate "what it is" sections from "how it works" sections. A section covering both structure and behavior is usually two sections collapsed into one.
Check the flow by asking: can a reader who skims only the section headings reconstruct the mental model? If the heading sequence reads like a component inventory rather than a conceptual arc, restructure.
Never-Do
- ⚠️ NEVER modify versioned docs unless explicitly requested
- ⚠️ NEVER run
npm run build (user runs when needed)
- ⚠️ NEVER use URL paths for links (use file paths with
.mdx)
- ⚠️ NEVER place admonitions inside JSX components like
<Step>
- ⚠️ NEVER add a
link field to _category_.json — sidebar section labels must expand/collapse only, not navigate
- ⚠️ NEVER create
README.mdx as a folder index in general docs sections — use overview.mdx with an explicit slug: instead. Exception: vcluster/configure/vcluster-yaml/ uses README.mdx because every folder is a yaml key and "Overview" is semantically wrong there (see Sidebar Navigation Conventions)
- ⚠️ NEVER recommend shared nodes for untrusted tenants with Kubernetes access or arbitrary workload execution (external, resale, regulated) — route those to private nodes (see Tenancy Model Positioning)
Always-Do
- ✅ Always run vale before finalizing documentation
- ✅ Always use file paths with
.mdx extension for links
- ✅ Always check vCluster terminology in
references/vcluster-terms.md
- ✅ Always use relative paths for versioned content
- ✅ Always add descriptive comments in YAML code blocks
Tenancy Model Positioning
When writing or editing any page that recommends or positions a worker node model, apply the shared-nodes rule (DOC-1616).
Core rule: Shared nodes provide control-plane, API, and namespace isolation, but tenant workloads share the same kernel and physical nodes. They are not a security boundary for untrusted tenants with Kubernetes access or arbitrary workload execution. The real axis is trust plus tenant access, not internal versus external.
- ✅ DO recommend shared nodes for internal, trusted tenants: development, testing, CI/CD, internal engineering teams, an enterprise sharing its own data center across its own teams.
- ✅ DO keep the internal production paths (Internal Kubernetes Platform, CI/CD Platform, Enterprise AI Factory shared tier) but pair every shared-node recommendation with the suitability caveat.
- ✅ DO route untrusted, external, or paying customers to private nodes (optionally vNode for runtime isolation).
- ❌ DON'T recommend shared nodes for externally facing or resold tenant offerings, AI-cloud/GPU customer tenancy, or any multi-tenant SaaS with untrusted tenants.
- ❌ DON'T lead with density or low-operational-overhead framing for shared nodes. Lead with isolation (consistent with the repositioning guidance in the project
CLAUDE.md).
- ❌ DON'T claim shared-node tenants get isolated nodes, networks, or infrastructure. That is only true for private nodes.
- ✅ DO frame NetworkPolicy positively as an added isolation layer worth enabling even for trusted tenants. vCluster can create the policies through
policies.networkPolicy, and the control plane cluster's CNI enforces them. Note the real-world failure mode is a silently ineffective control, most often a CNI that accepts NetworkPolicy resources without enforcing them, so point to the security baseline (/docs/vcluster/security) for verification.
Exception — provider-owned shared serving: The rule targets untrusted tenant workloads on shared nodes, not all multitenancy. A provider that runs its own trusted models on a shared serving tier, exposing only an API, is doing application-level multitenancy. This token-factory pattern (for example Nebius-style token serving) is not untrusted code on shared nodes, so it's allowed even for external, paying customers. Scope it explicitly to provider-owned, trusted models with application-level isolation, and never to customer-supplied models or code. The real distinction is trusted-provider-workload versus untrusted-tenant-workload, not internal versus external. Because of this, do NOT blanket-import the suitability admonition onto provider shared-serving pages. See vcluster/production-guide/inference-provider.mdx for the worked example.
Keep it principle-based: Frame shared-node risk as an architectural property (shared kernel and nodes) and a configuration responsibility, not as a vCluster defect. Never reference specific customers, their CNI or infrastructure choices, security incidents, conference reports, or found vulnerabilities in published docs. These positioning rules exist to prevent misconfiguration, not to document any single deployment.
Reuse the admonition: Import the shared suitability warning rather than rewriting it per page:
import SharedNodesSuitability from '@site/vcluster/_partials/admonitions/shared-nodes-suitability.mdx';
<SharedNodesSuitability />
Audit tip: When searching for shared-node positioning, grep more than the literal shared node. Also search shared infrastructure, dedicated node pools, shared tier, and shared node pool — different pages use different phrasings.
vCluster Terminology Quick Reference
Key terms (see references/vcluster-terms.md for complete guide):
- vCluster: The trademark (never "vClusters" - legally incorrect)
- tenant clusters: The clusters that vCluster creates ("virtual clusters" is retired); lowercase in prose
- control plane cluster: The cluster that hosts tenant cluster control planes ("host cluster" is retired); lowercase in prose
- vCluster Pro: Enhanced/paid tenant cluster with Pro capabilities
- vCluster Platform: Management platform and UI for tenant clusters
- vcluster: The CLI command name
Resources
scripts/
discover_partials.sh - Find all _partials, _fragments, _code directories
detect_language.sh - Map file extension to language identifier
run_vale.sh - Run vale linter with proper configuration
generate_import.sh - Generate correct import statement
references/
style-guide.md - Complete writing style guide from CONTRIBUTING.md
release-checklist.md - Critical rules for documentation releases
link-formatting.md - Link formatting rules with examples
partials-guide.md - Complete partials and components guide
mdx-components.md - MDX/JSX component usage and admonition rules
vcluster-terms.md - vCluster product terminology and naming