Publishing to GitHub Pages
Netdocs builds a plain static site, so it deploys anywhere that serves files. The repository ships a GitHub Actions workflow that builds this documentation site and publishes it to GitHub Pages.
The workflow
.github/workflows/docs.yml builds the CLI, runs netdocs build against
docs-site/appsettings.json, and uploads the output as a Pages artifact:
name: Docs
on:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: pages
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # git-revision-date needs full history
- uses: actions/setup-dotnet@v4
with:
global-json-file: global.json
- name: Build docs
run: dotnet run --project src/Netdocs.Cli -c Release -- build --prod --config docs-site/appsettings.json
- uses: actions/upload-pages-artifact@v3
with:
path: docs-site/site
deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- id: deployment
uses: actions/deploy-pages@v4
One-time setup
- In the repository Settings → Pages, set Source to GitHub Actions.
- Set
siteUrlindocs-site/appsettings.jsonto your Pages URL (e.g.https://<user>.github.io/<repo>/). - Push to
main— the workflow builds and deploys automatically. You can also trigger it manually via Run workflow (workflow_dispatch).
Publishing your own site
Use the same pattern: point the --config at your site's appsettings.json and upload
its siteDir. For a project-page URL under a subpath, make sure siteUrl includes the
subpath so links, the sitemap, and social cards resolve correctly.
Using the Netdocs GitHub Action
If your site lives in its own repository (i.e. you are not building from the Netdocs
source tree), you don't need .NET on the runner at all. The reusable
XtremeOwnage/Netdocs action downloads the
matching native netdocs binary from the releases page and runs it for you:
name: Docs
on:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: pages
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # git-revision-date needs full history
- name: Build with Netdocs
uses: XtremeOwnage/Netdocs@v1
with:
command: build
config: appsettings.json
args: --prod
# version: 1.0.0 # pin a release, or omit for 'latest'
- uses: actions/upload-pages-artifact@v3
with:
path: site
deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- id: deployment
uses: actions/deploy-pages@v4
Inputs:
| Input | Default | Description |
|---|---|---|
command |
build |
Netdocs subcommand (build, serve, import). |
config |
(none) | Path to the config file; passed as --config when set. |
version |
latest |
Release version to download (e.g. 1.0.0), or latest. |
args |
(none) | Extra arguments appended verbatim (e.g. --prod --strict). |
working-directory |
. |
Directory to run netdocs in. |
The action runs on Linux, Windows, and macOS runners (x64, plus Apple Silicon). Pin to a
major tag like @v1 for stability, or a full version tag for reproducible builds.
Built-in deploy targets
Instead of wiring up a workflow, netdocs can publish the build itself. Add a deploy
section to the Netdocs block of appsettings.json and run netdocs deploy (which builds
first, then publishes), or netdocs build --deploy.
Filesystem
Copy the built site to a local (or mounted) directory — handy for nginx web roots or a synced folder:
{
"Netdocs": {
"deploy": {
"target": "filesystem",
"path": "/var/www/docs",
"clean": true
}
}
}
path— destination directory (absolute, or relative to the project root).clean— whentrue(default), files at the destination that the build no longer produces are pruned.
Git branch
Publish the output to a branch (e.g. gh-pages) using a temporary git worktree, so your
main working tree is never touched:
{
"Netdocs": {
"deploy": {
"target": "git",
"branch": "gh-pages",
"remote": "origin",
"message": "Deploy docs",
"push": true
}
}
}
- The branch is created (as an orphan) on first deploy if it does not exist.
- Set
pushtofalseto commit locally without pushing. - Requires
gitonPATHand the project to be inside a git repository.
# Build and publish in one step
netdocs deploy --config appsettings.json
AWS S3
Sync the output to an S3 bucket for an AWS-hosted static site. This shells out to the
AWS CLI (aws s3 sync), so credentials and region are
resolved the standard AWS way (environment variables, ~/.aws/config, or an instance role):
{
"Netdocs": {
"deploy": {
"target": "s3",
"bucket": "my-docs-bucket",
"prefix": "docs",
"region": "us-east-1",
"clean": true
}
}
}
bucketis required;prefix(optional) publishes under a sub-path of the bucket.clean: truepasses--deleteso objects no longer produced by the build are removed.regionis optional — omit it to use the AWS CLI's configured default.- Requires the AWS CLI (
aws) onPATH.
Optimization
Enable HTML minification to shrink emitted pages (whitespace collapse + comment removal,
preserving pre/code/script/style), plus optional CSS and JavaScript minification of
copied assets:
{
"Netdocs": {
"optimize": {
"minifyHtml": true,
"minifyCss": true,
"minifyJs": true,
"convertImagesToWebp": true,
"webpQuality": 80
}
}
}
minifyCss/minifyJs strip comments and collapse whitespace in .css/.js assets as they
are copied into the output. The minifier is conservative — it never renames identifiers or
reorders rules, and it preserves string, template, and URL contents verbatim. Files that are
already minified (*.min.css, *.min.js) are copied through untouched.
WebP image conversion
convertImagesToWebp emits a .webp sibling next to every copied .png/.jpg/.jpeg
image and rewrites local <img> references into a <picture> element:
<picture>
<source srcset="images/diagram.webp" type="image/webp">
<img src="images/diagram.png" alt="Diagram">
</picture>
This is non-destructive — the original raster file is kept and referenced as the
<img> fallback, so browsers without WebP support still render correctly while modern
browsers download the smaller WebP. webpQuality (1–100, default 80) controls the
encoder quality. Conversions are cached by source content hash under .cache/webp/, so
unchanged images are not re-encoded on subsequent builds. Remote images (http://,
https://, //, data:), SVGs, and images already in WebP format are left untouched.
Incremental builds and deploys
Netdocs avoids redundant work through content-hash caches rather than a bespoke deploy manifest:
- Render cache (
.cache/render.json) keys each page's rendered HTML/TOC on its processed markdown, the pipeline configuration, and the link map. Unchanged pages skip the expensive parse/render step on the next build. - WebP cache (
.cache/webp/) keys converted images on source content hash + quality, so unchanged images are never re-encoded. - Deploy transfer is already incremental at the backend: the git branch target is
content-addressed and only commits changed blobs, and the S3 target uses
aws s3 sync, which compares size/modified time and uploads only changed objects.
Because the deploy backends already skip unchanged files, Netdocs does not publish a separate file-hash manifest to the publish branch — doing so would duplicate git/S3 behavior. Remember that navigation or metadata changes invalidate a page's render-cache entry (the link map hash changes), so those pages are correctly rebuilt.
Other hosts
The site/ output is static HTML/CSS/JS. Upload it to any static host (Netlify, S3 +
CloudFront, nginx, etc.). Point the host at the build output directory.