Docforge is a Documentation-As-Code enabling command-line tool that reproducibly forges source documentation into publishable documentation bundles, using desired documentation state declarations called documentation manifests. A documentation manifest includes structured references to source documentation files and rules for fetching sources. All links within the Markdown documents are adjusted automatically according to declared structure. Embeddable resources, like images, are download and packed into the bundles.
Docforge currently supports GitHub and GitHub Enterprise as remote source hosts (github.com and self-hosted GitHub Enterprise instances). It was designed to solve the outstanding issue for multi-repo projects that want to maintain documentation in a distributed manner and yet release aggregated, coherent bundles out of it with minimal effort.
Docforge is designed to support the re-purposing of documentation sources. Instead of designing documentation structures for a particular tool or platform, a single one is sufficient to produce multiple documentation bundles from it, each described in its own manifest, and targeting a particular publishing channel or purpose. The tool goes even further supporting the creation of completely new documents from existing sources by aggregations.
Docforge manifests are modular, supporting references to other manifests that are included recursively for maintaining potentially complex structures, e.g. for large documentation portals.
Figure 1: Docforge overviewFrom Documentation-as-Code tool chain perspective, Docforge is the tool that makes source documentation available for further transformation, processing and publishing.
Figure 2 shows one of many options to build a Documentation-as-Code automated process, orchestrated by CI/CD, focusing on the role of Docforge. In this particular example, source documentation resides in multiple repositories and needs to be built as a static HTML website with a static site generator, and then pushed to a repository configured to be served by GitHub Pages. When a new release is triggered, Docforge will use the released version of the documentation manifest dedicated to publishing with GitHub Pages to forge a bundle for this release. The bundle will then be used as input content by the next tool in the build process — the static site generator.
Figure 2: Sample documentation-as-code tool chain, including Docforge as step 1 in the documentation build processAt a glance:
- Declarative
- Document selection rules support
- Composable manifests that can include references to other manifests recursively
- Designed to forge from distributed, remote documentation sources
- Abstracts source documentation to re-purpose it into documentation bundles targeting various platforms and tools
- Efficient operation
- out-of-the-box, optional support for Hugo and other static site generators
- out-of-the-box, support for GitHub and GitHub Enterprise
Go to the latest release and download the binary for your OS and architecture:
| OS | Architecture | Binary name |
|---|---|---|
| macOS | Intel (x86_64) | docforge-darwin-amd64 |
| macOS | Apple Silicon (arm64) | docforge-darwin-arm64 |
| Linux | x86_64 | docforge-linux-amd64 |
| Linux | arm64 | docforge-linux-arm64 |
| Windows | x86 | docforge-windows-386 |
Make the binary executable and place it on your PATH. Example for Linux/macOS:
# Replace <VERSION> and <BINARY> with the values for your platform
curl -Lo /usr/local/bin/docforge \
https://github.com/gardener/docforge/releases/latest/download/<BINARY>
chmod +x /usr/local/bin/docforgeDisclaimer on releases: Until there is a stable 1.0 version changes are likely to occur and not necessarily backwards compatible. New features are released with a minor version increase. We do not release hotfixes except for the latest minor release, only for bugs and only when critical.
Docker images with all docforge releases are public at Google Artifact Registry. To pull a docforge image for a release use the release as image tag, e.g. for docforge version v0.58.0:
docker pull europe-docker.pkg.dev/gardener-project/releases/docforge:v0.58.0go install github.com/gardener/docforge@latestGitHub API rate limits: docforge uses the GitHub API to fetch content. Unauthenticated requests are limited to 60 per hour per IP. It is strongly recommended to supply a personal access token via
--github-oauth-env-map.
Get from zero to a real output bundle. Every command is copy-pasteable — the only edit you need is pasting your token in step 1.
1. Create a GitHub token and export it
Go to github.com → Settings → Developer settings → Personal access tokens → Tokens (classic), create a token with repo (read) scope, then:
export GITHUB_TOKEN=ghp_yourTokenHere2. Dry run — inspect the resolved node tree
docforge \
-f https://github.com/gardener/docforge/blob/master/example/getting-started.yaml \
-d /tmp/docforge-dry \
--github-oauth-env-map github.com=GITHUB_TOKEN \
--dry-runThis prints the resolved manifest node tree to stdout. --dry-run does not prevent files from being written — downloads still run and files land in /tmp/docforge-dry. Use a separate throwaway directory (as above) so the real run in step 3 starts clean.
3. Real run — write the bundle
docforge \
-f https://github.com/gardener/docforge/blob/master/example/getting-started.yaml \
-d /tmp/docforge-out \
--github-oauth-env-map github.com=GITHUB_TOKENYou should now see (the tree reflects master at run time):
/tmp/docforge-out/
├── README.md
└── docs/
├── cmd-ref/
│ └── docforge_gen-toc.md
├── consistency.md
├── how-to.md
├── images/
│ ├── docforge-overview.svg
│ └── ...
├── manifest-ref.md
├── manifests.md
└── user-index.md
With markdown-enabled: true in ~/.docforge/config, README.md would instead be written as _index.md.
What just happened? See Working with Documentation Manifests and How-to guides for a deeper walkthrough.
| If you see… | It means / fix |
|---|---|
github.com's OAUTH ENV variable is empty |
The env var named in --github-oauth-env-map is set but empty. Make sure export GITHUB_TOKEN=... was run in the same shell session before invoking docforge. |
no resource handlers were loaded. Is the config yaml file correct? |
--github-oauth-env-map was not provided or every entry failed (e.g. all tokens empty). Add --github-oauth-env-map github.com=YOUR_ENV_VAR and make sure the env var is exported. |
Passing a local path to -f (e.g. -f ./manifest.yaml) produces a "no resource handlers" error or resolves nothing |
Manifests are resolved through the repository-host abstraction — local filesystem paths are not supported directly. Use a GitHub blob URL (e.g. https://github.com/org/repo/blob/main/manifest.yaml) or add a resourceMappings entry in ~/.docforge/config to map a GitHub URL prefix to a local directory. See Manifest URLs must be accessible via a registered host. |
.md files in the output have unrewritten links, no frontmatter, or are not renamed to _index.md |
markdown-enabled is false by default — .md files are copied as raw bytes with no processing at all. Add markdown-enabled: true to ~/.docforge/config. This is a config-file-only setting; there is no CLI flag. See Non-Hugo configuration. |
--dry-run was passed but files were still written to --destination |
This is expected. --dry-run prints the node tree and suppresses --clean-destination; it does not skip downloads or writes. If you want no output, omit --destination — but note that omitting it causes writes to the current working directory (FSWriter uses it as the root). The safest option is a throwaway directory. |
| Command | Description |
|---|---|
docforge |
Forge a documentation bundle from a manifest |
docforge gen-toc |
Generate a navigation YAML from a manifest |
docforge gen-cmd-docs |
Generate command reference documentation |
docforge version |
Print the version |
To create a documentation bundle, describe its structure in a manifest file. See Working with Documentation Manifests for the full manifest syntax, or the manifest reference for all supported fields.
A minimal example manifest is provided at example/getting-started.yaml.
Docforge uses the GitHub API to fetch content. Unauthenticated requests are limited to 60 per hour per IP. Set up a personal access token and export it:
export GITHUB_TOKEN=<your-token>docforge \
-d /tmp/docforge-out \
-f example/getting-started.yaml \
--github-oauth-env-map github.com=GITHUB_TOKENUse --dry-run to print the resolved manifest node tree to stdout without cleaning the destination. Note: --dry-run does not prevent files from being written — node processing and downloads still run. It only suppresses --clean-destination and prints the resolved node tree:
docforge \
--dry-run \
-f example/getting-started.yaml \
--github-oauth-env-map github.com=GITHUB_TOKENAll available flags are documented in the command reference.
The following flags have non-obvious defaults or behavior that is easy to miss:
| Flag | Default | What it actually does |
|---|---|---|
--hugo |
true |
Enables Hugo-specific processing on every build. Pass --hugo=false for non-Hugo targets. |
--hugo-pretty-urls |
true |
Currently has no effect. The field is registered but not consumed by the link resolver — pretty-URL rewriting is controlled solely by --hugo. |
--hugo-section-files |
[readme.md, README.md] |
Files matching these names are renamed to _index.md in the output. |
--content-files-formats |
(empty) | When empty, all file types pass through. When set (e.g. .md), only files with matching extensions are included. |
--clean-destination |
false |
When set, removes the destination directory before writing. Ignored with --dry-run. |
--aliases-enabled |
false |
Enables Hugo alias propagation from dir frontmatter to child files. |
--docsy-edit-this-page-enabled |
false |
Adds Docsy "Edit this page" frontmatter fields to output files. |
--github-info-destination |
(empty) | When set, writes a .json sidecar per source file containing GitHub commit metadata (author, contributors, last modified date, publish date, SHA). |
gen-toc is a separate command with a distinct purpose: instead of fetching and writing document content, it reads a manifest and derives a navigation structure from it. The result is a YAML file that can be consumed by VitePress, MkDocs, or other site generators that accept a nav file — without running a full forge.
docforge gen-toc \
-f example/getting-started.yaml \
--github-oauth-env-map github.com=GITHUB_TOKENWrite to a file instead of stdout:
docforge gen-toc \
-f example/getting-started.yaml \
--github-oauth-env-map github.com=GITHUB_TOKEN \
-o toc.yamlThe output is a YAML file with a nav key containing nested entries. Each entry has a title (resolved from the manifest or document frontmatter) and a filename (the path within the output bundle). Sections with children include a subnav list:
nav:
- title: Installation Guide
filename: Installation/README.md
subnav:
- title: Quick Start Guide
filename: Installation/quickstart.md
- title: Advanced
filename: Installation/advanced.md
- title: Operations
filename: Operations/README.md
subnav:
- title: Gardener Operator
filename: Operations/gardener-operator/README.md
subnav:
- title: Api Resources
filename: Operations/gardener-operator/api-resources.md
- title: Troubleshooting
filename: Troubleshooting/README.mdPaths in filename are relative to the bundle root (the --destination directory of the corresponding forge run). Use --strip-root to remove the top-level directory prefix from all paths.
Title resolution order (first match wins):
frontmatter.titlein the manifest nodetitlein the document's own frontmatter (read from the source.mdfile)- Filename with hyphens/underscores replaced by spaces, Title case
| Flag | Default | Description |
|---|---|---|
-f, --manifest |
(required) | Manifest URL or local path |
-o, --output |
stdout | Output file path |
--index-file-names |
[readme.md, README.md, index.md] |
Filenames treated as section index (promoted to section entry) |
--strip-root |
false |
Strip the top-level directory prefix from all output paths |
--github-oauth-env-map |
(empty) | GitHub token map, same format as the forge command |
When targeting a non-Hugo site generator or a plain file output, use the following configuration (in ~/.docforge/config or passed via DOCFORGE_CONFIG):
hugo: false
hugo-section-files: []
markdown-enabled: truehugo: false— disables all Hugo-specific processing: files are written with their original names and links are not rewritten to pretty-URL format.hugo-section-files: []— preventsreadme.md/README.mdfrom being renamed to_index.md. This setting only takes effect whenhugo: true; it is included here so that enabling Hugo later does not accidentally rename your index files.markdown-enabled: true— required for Markdown processing to work at all. When false (the default),.mdfiles are copied as raw bytes with no link rewriting, no frontmatter propagation, and no Hugo transformations. This is a config-file-only setting; it has no corresponding CLI flag.