Skip to main content

Installing the Docs System

How to add this documentation system to a project that is not this repository. Everything below runs from the published container image — no Node install, no template checkout to keep in sync.

The same content is written to be pasted into a consumer project's AGENTS.md, so an agent working in that repository has it to hand.

Install

docker run --rm -v "$PWD:/work" -w /work --user "$(id -u):$(id -g)" \
ghcr.io/the-running-dev/docs-template:latest \
Invoke-SetupDocs -ProjectDir /work -Title 'My Project' -SiteUrl 'https://docs.example.com/'
  • Mount the whole project, including .git. The documentation gate finds the project root by walking up for a .git marker and fails without it.
  • --user "$(id -u):$(id -g)" matters on Linux hosts; without it the container writes root-owned files into the repository.
  • -ProjectDir must point at the mount. It defaults to ., and the image's own working directory is /template. Omit both and the command refuses to run rather than installing into the image itself.

Re-run with -Overwrite to pick up upstream fixes. -BaseImage pins a specific image tag instead of tracking :latest, and is written to all four places an install references the image.

By default the Docusaurus overlay lives at docs/. Pass -DocsDirectory documentation (a single directory name — no spaces, quotes, or nested paths) to put it somewhere else — every generated path, command, gate rule, and workflow follows that choice, so nothing needs hand-adjusting afterward. A re-run that omits -DocsDirectory detects and keeps whichever directory is already installed, the same way an existing -RouteBasePath is preserved; pointing it at a different directory than the one already installed is refused, not migrated — rename the directory yourself (git mv docs documentation) and re-run. The installer owns whichever directory you choose: do not reproduce its overlay behavior (the README-to-root generation, the src/pages strip-then-overlay, the workflow build step) in a hand-written workflow of your own — re-run the installer instead, so a later template fix reaches your project the same way it reaches every other consumer.

Other commands the image exposes:

CommandPurpose
Invoke-SetupDocsInstall or update the whole system.
Invoke-SetupDocsWorkflowInstall only the two workflows, leaving everything else.
Invoke-DocsBuildBuild the static site, the same way CI does.
Invoke-DocsTestRun the documentation gate.

Invoke-DocsBuildImage and Invoke-PreviewDocs are in the module too, but they drive Docker themselves, so they only run on a host — not inside the image.

What gets installed

<docs dir> below is docs/ unless -DocsDirectory says otherwise.

PathNotes
<docs dir>/docs/**Authored Markdown. Add pages here.
<docs dir>/docs/index.mdGenerated from README.md. Do not edit.
<docs dir>/docusaurus.config.tsSite title, URL, navbar, broken-link policy.
<docs dir>/sidebar.tsSidebar structure. Note the singular filename.
<docs dir>/DockerfileFROM the base image, COPY . . to overlay this folder.
<docs dir>/.dockerignoreKeeps the build context to the overlay.
docs.ps1Local preview entry point.
build/ConvertTo-DocumentationHomepage.ps1README to homepage generator.
build/Test-Documentation.ps1The documentation gate.
.config/DocumentationRules.psd1Gate rules: terminology, exclusions, generated-file drift.
.github/workflows/docs-ci.ymlGate and build verification.
.github/workflows/docs-deploy.ymlBuild and deploy to GitHub Pages.

Upgrading from a version that installed four workflow files also deletes the two now retired, docs.yml and docs-quality.yml. They are not merely redundant: docs.yml drives the others with uses: and neither declares workflow_call any more, and docs-quality.yml reports a check whose name collides with the gate job in docs-ci.yml.

Deploying

The workflows do the work; two things must be set up once, by hand, because the installer cannot do them.

1. Enable GitHub Pages. Repository → SettingsPagesSource: GitHub Actions. Without this the deploy job fails at configure-pages.

2. Make the checks required on the default branch, or a red run reports but does not block a merge. Require exactly these two — the ones that run on a pull request, named as CI reports them:

Documentation links and terminology
Verify Documentation Build

Do not require Build and Deploy Documentation. It comes from docs-deploy.yml, which triggers only on push to main, so it never reports on a pull request. Requiring it leaves every pull request waiting forever on a check that will not arrive.

No registry credentials are needed: ghcr.io/the-running-dev/docs-template is a public package, so the github.token the workflows already fall back to is enough. The REGISTRY_TOKEN secret they also accept is only required when -BaseImage points at a private fork or mirror.

Then the flow is automatic:

  • Pull request → the gate runs and the site builds, archiving the Pages artifact. Nothing is published.
  • Push to maindocs-deploy.yml builds the site in the base image, uploads the Pages artifact, and deploys it.

The published URL is the url plus baseUrl in docs/docusaurus.config.ts. -SiteUrl sets url onlybaseUrl keeps the template's /, and is independent of -RouteBasePath. For a GitHub Pages project site, served at https://<owner>.github.io/<repo>/, that is wrong and every asset 404s, so set baseUrl: '/<repo>/' by hand after installing. A user or organisation site, or a custom domain served from the root, can leave it alone. Deployments appear under the repository's Environments → github-pages.

To reproduce what CI builds, without pushing:

docker run --rm -v "$PWD:/work" -w /work \
ghcr.io/the-running-dev/docs-template:latest \
Invoke-DocsBuild -SourceDocs /work/docs -OutputPath /work/artifacts/docs

-SourceDocs must point at whatever -DocsDirectory the project installed with — the generated workflows already pass the matching value; this is only for reproducing their build by hand.

Note the missing --user here, unlike the install command above. Invoke-DocsBuild overlays your docs/ onto the image's root-owned /template before building, so a non-root user cannot write there and the run fails with Access to the path '/template/Dockerfile' is denied. Invoke-SetupDocs writes into the mount instead, which is where --user matters.

Local preview

./docs.ps1 # build and serve
./docs.ps1 -Live # bind-mount docs/ for hot reload
./docs.ps1 -BuildOnly # build only; regenerates the homepage

Requires Docker and nothing else. -Port, -Tag, and -BaseImage are also available. The homepage is regenerated on every run unless -NoHomepage is passed.

The homepage is generated

docs/docs/index.md is produced from README.md. The gate re-runs the generator and fails if the committed copy differs, so a hand edit cannot survive.

To change the homepage, edit README.md, then run ./docs.ps1 -BuildOnly and commit both together. A README change committed without the regenerated homepage fails the gate with GeneratedFile: Generated from 'README.md' but the committed copy differs.

The title, description, and site origin the generator uses live in the GeneratedFiles block of .config/DocumentationRules.psd1, not on the command line, so the preview script and the gate cannot disagree about what the homepage should contain.

Relative links in the README

The generator rewrites the published site origin to /, but does not rewrite relative links. A link valid at the project root breaks once the content is copied two directories deeper — docs/guide.md becomes docs/docs/docs/guide.md and the gate reports a MarkdownLink error. Prefer absolute links to the published site for anything the homepage needs to reach.

The documentation gate

./build/Test-Documentation.ps1
./build/Test-Documentation.ps1 -Path README.md

Those need PowerShell on the host. Without it, run the gate from the image:

docker run --rm -v "$PWD:/work" -w /work --user "$(id -u):$(id -g)" \
ghcr.io/the-running-dev/docs-template:latest Invoke-DocsTest
RuleSeverityMeaning
MarkdownLinkErrorRelative link target does not exist on disk.
MarkdownAnchorError#fragment matches no heading in the target document.
GeneratedFileErrorA generated file no longer matches its source.
TerminologyWarningProduct name cased inconsistently, e.g. GithubGitHub.

Errors fail the run, locally and in CI. Warnings are reported but do not fail anything, because the installed docs-ci.yml runs the gate without -TreatWarningsAsErrors. Add that switch to the gate step if a terminology slip should block a merge.

Fenced blocks, inline code spans, link targets, and bare URLs are blanked before the rules run, so a sample that deliberately shows Github is not a finding. If prose is flagged that should not be, edit .config/DocumentationRules.psd1 rather than working around the gate.

Serving path

Documentation is served from the site root by default — the installer writes routeBasePath: '/', so the README-derived docs/docs/index.md is your landing page at /.

Pass -RouteBasePath docs to serve under /docs instead. Two things to know if you do:

  • / still resolves — the installer generates it, you do not have to. The README is written to docs/src/pages/index.md as a real page route, so the theme's 404 page and docs navbar links to / both resolve. docs/docs/index.md keeps resolving too, as a minimal landing page rather than a second copy of the README, so /docs/ is never left without a page. Neither file needs hand-authoring; both come from the same install.
  • The homepage generator rewrites the published site origin to /, which is exactly where it now renders under this shape, so the rewritten links land correctly on the generated page.

Neither distinction applies on the default (routeBasePath: '/'), where the docs index already is the root and there is only one generated file. Re-running the installer with -Overwrite will not move an existing project: it keeps whatever routeBasePath your config already has, and says so, unless you pass -RouteBasePath explicitly.

The template's own pages are not part of your site. Earlier images compiled src/pages from the image into every consumer build, so sites inherited /, /cv, /portfolio, /projects and /admin/projects under their own title. They are no longer shipped, and the build removes any that a stale image still carries.

Before committing documentation changes

  1. Edited README.md? Run ./docs.ps1 -BuildOnly and commit the regenerated docs/docs/index.md alongside it.
  2. Run ./build/Test-Documentation.ps1 and resolve every error. Warnings do not block CI, but fix them anyway — nothing else will.
  3. Added a page under docs/docs/? Confirm it appears where you expect in the sidebar.