Build and publish container registry descriptions from project Markdown
603
Git project: WoozyMasta/regdoc
License: MIT
regdoc combines Markdown documentation from a project into one document,
normalizes links and structure,
and publishes the result to the container registry alongside the image.
It lets you reuse README files, changelogs, links, and metadata from the source repository and keep them available to users in Docker Hub, Quay, or Harbor.
Download a binary for your platform from the releases page, or install it with Go:
go install github.com/woozymasta/regdoc/cmd/regdoc@latest
ghcr.io/woozymasta/regdoc:latest
docker.io/woozymasta/regdoc:latest
By default, regdoc finds README.md and CHANGELOG.md in the work directory,
merges them, and publishes the result.
The provider is determined from the image hostname.
Docker Hub uses a username with a password or personal access token:
REGDOC_USERNAME="example" REGDOC_TOKEN="$DOCKERHUB_TOKEN" \
regdoc example/service
Publishing a Docker Hub description requires a PAT with Delete permission;
image push permission alone is not sufficient.
For Quay, provide an OAuth token:
REGDOC_TOKEN="$QUAY_TOKEN" \
regdoc quay.io/example/service
Harbor requires a username and password:
REGDOC_USERNAME="example" REGDOC_PASSWORD="$HARBOR_PASSWORD" \
regdoc registry.example/team/service
Docker Hub and Harbor also use the Docker credential store
and credential helpers when explicit credentials are absent.
Select --provider explicitly when the registry hostname is ambiguous.
Do not pass secrets as command-line arguments.
Use environment variables, --password-stdin, or --token-stdin.
The registry description is a single field per repository,
shared across every tag.
When parallel release lines (e.g. a 3.x and a 2.x branch)
build independently, CI can publish out of version order
and leave the description showing older docs than what was already there.
Add an explicit tag to IMAGE to guard against this:
regdoc quay.io/example/service:3.1.1
When IMAGE carries an explicit tag,
regdoc lists the tags already published in the repository -
using the same credentials already resolved for publishing,
no extra permissions needed - finds the highest existing stable release,
and skips publishing (a no-op, not an error) if tag being published is older.
Equal versions still publish, so re-running the same tag's CI job is safe.
A tag that isn't a valid stable SemVer release
(:latest, :nightly, :sha-abcdef, prereleases)
is treated the same as no tag at all: publish unconditionally.
Without an explicit tag, behavior is unchanged from today.
--skip-tag-check bypasses the gate and publishes regardless of tag order.
--version-format selects how tags are compared:
semver (the default, shown above),
calver, numeric, lexical, pep440, debian, or rpm.
--calver-format picks the calendar layout when --version-format=calver:
regdoc --version-format=calver --calver-format=ym-dot \
quay.io/example/service:2026.07
A tag that does not match the configured format is treated the same as no tag at all: publish unconditionally.
The generated header shows a Release line, when a version is known.
--release-version sets it explicitly;
without it, regdoc falls back to IMAGE's explicit tag
(the same tag the stale-publish gate above reads),
shown as-is with no validation:
regdoc --release-version=3.1.1 quay.io/example/service
When the source forge and project URL are known (see "Documents, links, and images" below), the release is linked to that version's tag page in the forge - not a Releases page, since a Release object is optional and a git tag is not. Without a known forge or without any version at all, the line is either plain text or omitted entirely.
This example selects the provider explicitly, supplies project metadata, and uses a known corporate registry limit. Files are published in the listed order.
REGDOC_TOKEN="$QUAY_TOKEN" \
regdoc \
--provider=quay \
--title="$CI_PROJECT_TITLE" \
--source-url="$CI_PROJECT_URL" \
--short-description="$CI_PROJECT_DESCRIPTION" \
--doc-body-limit=65536 \
--cut-heading-level=2 \
--cut-retries=3 \
quay.io/example/service \
README.md docs/*.md CHANGELOG.md
docs/*.md is expanded by regdoc itself, including in PowerShell.
Matches are added in lexical order.
Note
Supplying an explicit file list disables automatic selection of `README.md` and `CHANGELOG.md`.
GitHub Actions runs the job in the image:
jobs:
publish-documentation:
runs-on: ubuntu-latest
container: ghcr.io/woozymasta/regdoc:latest
steps:
- uses: actions/checkout@v6
- env:
REGDOC_TOKEN: ${{ secrets.QUAY_TOKEN }}
run: regdoc quay.io/example/service README.md 'docs/*.md' CHANGELOG.md
GitLab CI requires an empty entrypoint so the runner can start its shell:
publish-documentation:
image:
name: ghcr.io/woozymasta/regdoc:latest
entrypoint: [""]
script:
- regdoc quay.io/example/service README.md 'docs/*.md' CHANGELOG.md
Set REGDOC_TOKEN, REGDOC_USERNAME, and REGDOC_PASSWORD
as protected CI variables when required by the target registry.
--output disables publishing:
no registry detection, credential lookup, or network requests occur.
Write Markdown to stdout:
regdoc --output - quay.io/example/service
Save HTML:
regdoc --format html --output description.html quay.io/example/service
HTML is useful for registries with limited Markdown support, such as legacy Quay UI versions that do not render tables.
Without explicit files, regdoc finds README.md and CHANGELOG.md
under --root and adds them in that order.
An explicit list disables autodiscovery:
regdoc quay.io/example/service README.md docs/*.md CHANGELOG.md
--link-base-url and --image-base-url turn relative file
and image links into source repository links.
A Markdown link needs a different route than a Markdown image
on every supported forge, so the two are separate flags:
set both or neither, setting only one is a configuration error.
When both are absent, regdoc determines them from CI metadata,
trying providers in this order and stopping at the first one
whose CI sentinel matches -
an incomplete profile leaves relative links untouched
rather than falling back to another provider or a default branch:
GITLAB_CI=true):
CI_PROJECT_URL and CI_COMMIT_SHA;
links use -/blob/<sha>/, images use -/raw/<sha>/.BITBUCKET_BUILD_NUMBER set):
BITBUCKET_GIT_HTTP_ORIGIN and BITBUCKET_COMMIT;
links use src/<sha>/, images use raw/<sha>/.FORGEJO_ACTIONS=true, Forgejo Runner v7.0.0+ only):
FORGEJO_SERVER_URL, FORGEJO_REPOSITORY, FORGEJO_SHA,
falling back per-field to the GITHUB_*-compatible aliases Forgejo also sets;
links use src/commit/<sha>/, images use raw/commit/<sha>/.
Older runners never set FORGEJO_ACTIONS
and are picked up by the GitHub Actions profile below instead -
there is no reliable way to tell them apart.GITEA_ACTIONS=true):
GITHUB_SERVER_URL, GITHUB_REPOSITORY, GITHUB_SHA
(Gitea has no native variables of its own for these);
same route shape as Forgejo.GITHUB_ACTIONS=true):
GITHUB_SERVER_URL, GITHUB_REPOSITORY, GITHUB_SHA;
links use blob/<sha>/, images use raw/<sha>/.CI_FORGE_TYPE set):
CI_REPO_URL and CI_COMMIT_SHA, with the route shape
selected by the reported forge type
(github, gitlab, gitea, forgejo, bitbucket, bitbucket_dc).All discovered links are pinned to a full commit SHA, never a branch name. An unsupported forge, a proxy, or a third-party CI system with no forge-identifying variable (Drone, CircleCI, Jenkins, and similar) can opt in explicitly:
regdoc \
--link-base-url="https://git.example/team/project/blob/0123456789abcdef/" \
--image-base-url="https://git.example/team/project/raw/0123456789abcdef/" \
quay.io/example/service
These bases include the forge route and revision.
The source project URL shown in the header
is configured separately with --source-url.
The same override also covers a deliberate choice to point documentation at a mutable ref instead of the pinned commit, e.g. always linking to a project's default branch:
regdoc \
--link-base-url="$CI_PROJECT_URL/-/blob/$CI_DEFAULT_BRANCH/" \
--image-base-url="$CI_PROJECT_URL/-/raw/$CI_DEFAULT_BRANCH/" \
quay.io/example/service
The same CI data populates the generated header
when --title, --source-name, and --source-url are not set:
CI_PROJECT_TITLE, CI_PROJECT_PATH, CI_PROJECT_URL.BITBUCKET_REPO_SLUG, BITBUCKET_REPO_FULL_NAME,
and BITBUCKET_GIT_HTTP_ORIGIN.GITHUB_SERVER_URL and GITHUB_REPOSITORY
(or the native FORGEJO_* variables when Forgejo sets them);
the title is the final repository path component.CI_REPO_URL; the title is the final path component.Explicit --title, --source-name,
and --source-url always win over discovered metadata.
For private projects, local images can be embedded as base64 in the document:
regdoc --embed-images quay.io/example/service
Only files inside --root are embedded; external URLs remain unchanged.
Data URIs increase description size, so account for selected registry limit.
Important
In addition to their size, not every registry can render base64 images in document links. Verify the result with the target registry.
The complete option list and default values are available in CLI.md and in the command itself:
regdoc --help
regdoc docs md -
regdoc - Publish repository documentation to container registry descriptions.
regdoc [OPTIONS]
regdoc collects repository Markdown and publishes it as the description of a Docker Hub, Quay or Harbor container registry.
| Option | Description | Default | Environment | Required |
|---|---|---|---|---|
--short-description | Short description, where supported by the provider | $REGDOC_SHORT_DESCRIPTION | no | |
--fallback | Behavior when the payload is too large; choices: none, cut | cut | $REGDOC_FALLBACK | no |
-o, --output | Write generated content without publishing; use - for stdout | $REGDOC_OUTPUT | no | |
--format | Output format: md or html; choices: md, html | md | $REGDOC_FORMAT | no |
--doc-body-limit | Maximum rendered document body size in bytes; zero uses the provider default | 0 | $REGDOC_DOC_BODY_LIMIT | no |
--cut-heading-level | Prefer a heading from level 1 through this level as the cut boundary | 2 | $REGDOC_CUT_HEADING_LEVEL | no |
--cut-retries | Maximum additional publish attempts after a payload-too-large response | 5 | $REGDOC_CUT_RETRIES | no |
--embed-images | Embed local images as base64 data URIs | $REGDOC_EMBED_IMAGES | no |
| Option | Description | Environment | Required |
|---|---|---|---|
--title | Project title used in the generated header | $REGDOC_TITLE | no |
--source-name | Project name shown in the generated header | $REGDOC_SOURCE_NAME | no |
--source-url | Project URL shown in the generated header | $REGDOC_SOURCE_URL | no |
--release-version | Release version shown in the generated header; defaults to IMAGE's explicit tag | $REGDOC_RELEASE_VERSION | no |
--license | License file to identify in the header (default: auto-discover LICENSE in --root) | $REGDOC_LICENSE | no |
--author | Author shown in the generated header | $REGDOC_AUTHOR | no |
--copyright | Copyright notice shown in the generated header | $REGDOC_COPYRIGHT | no |
--link-base-url | Base URL prepended to relative Markdown link destinations; overrides CI URL discovery (requires --image-base-url) | $REGDOC_LINK_BASE_URL | no |
--image-base-url | Base URL prepended to relative Markdown image destinations; overrides CI URL discovery (requires --link-base-url) | $REGDOC_IMAGE_BASE_URL | no |
--keep-comments | Keep HTML comments instead of stripping them from the published Markdown | $REGDOC_KEEP_COMMENTS | no |
| Option | Description | Default | Environment | Required |
|---|---|---|---|---|
-p, --provider | Provider: auto, dockerhub, quay or harbor; choices: auto, dockerhub, quay, harbor | auto | $REGDOC_PROVIDER | no |
-r, --root | Root directory for document discovery and relative link resolution | . | $REGDOC_ROOT | no |
--plain-http | Use plain HTTP for the target registry | $REGDOC_PLAIN_HTTP | no | |
--tls-skip-verify | Disable TLS certificate verification for the target registry | $REGDOC_TLS_SKIP_VERIFY | no |
| Option | Description | Environment | Required |
|---|---|---|---|
--username | Registry username | $REGDOC_USERNAME | no |
--password | Registry password (prefer --password-stdin or REGDOC_PASSWORD: CLI arguments are visible to other processes) | $REGDOC_PASSWORD | no |
--token | API token (prefer --token-stdin or REGDOC_TOKEN: CLI arguments are visible to other processes) | $REGDOC_TOKEN | no |
--password-stdin | Read the password from stdin | $REGDOC_PASSWORD_STDIN | no |
--token-stdin | Read the token from stdin | $REGDOC_TOKEN_STDIN | no |
| Option | Description | Default | Environment | Required |
|---|---|---|---|---|
--version-format | Version format used to compare IMAGE's tag against existing tags; choices: semver, calver, numeric, lexical, pep440, debian, rpm | semver | $REGDOC_VERSION_FORMAT | no |
--calver-format | Built-in CalVer layout used when --version-format=calver; choices: ymd-dash, ymd-dot, ym-dot, ym-short | ymd-dash | $REGDOC_CALVER_FORMAT | no |
--timeout | Per-request HTTP timeout | 30s | $REGDOC_TIMEOUT | no |
--optional | Treat missing documents, credentials or repository as a successful no-op | $REGDOC_OPTIONAL | no | |
--skip-tag-check | Publish even if IMAGE's tag is older than the highest already-published stable tag | $REGDOC_SKIP_TAG_CHECK | no | |
--debug | Print technical diagnostics to stderr | $REGDOC_DEBUG | no | |
-q, --quiet | Suppress informational output (errors are still printed) | $REGDOC_QUIET | no |
| Option | Description | Required |
|---|---|---|
-h, --help | Show this help message | no |
-v, --version | Show version information | no |
Help Commands
Show help
Usage: regdoc [OPTIONS] help
Show version information
Usage: regdoc [OPTIONS] version [version-OPTIONS]
| Option | Description | Required |
|---|---|---|
--short | Print version number only | no |
--commit | Print commit SHA only | no |
--json | Print version information as JSON | no |
Generate shell completion
Usage: regdoc [OPTIONS] completion [completion-OPTIONS]
| Option | Description | Required |
|---|---|---|
--shell | Shell completion format; choices: bash, zsh, pwsh | no |
| Name | Description | Required |
|---|---|---|
output | Output file path | no |
| Name | Description | Required |
|---|---|---|
IMAGE | Container repository reference | yes |
MARKDOWN | Markdown files or glob patterns to publish, in order | no |
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
go-containerregistry with ORAS for image-reference validation
and Docker-compatible host credential lookup.goldmark-markdown with a maintained internal CommonMark renderer
compatible with Goldmark 1.8.5.IMAGE tag
is older than the highest already-published tag,
--skip-tag-check to bypass gate,
--version-format/--calver-format to pick the comparison format.--release-version show a Release line in the generated header,
linked to that version's tag page when the source forge is known.
Defaults to IMAGE's explicit tag.--base-url with --link-base-url/--image-base-url,
since links and images need different routes on every forge.Content type
Image
Digest
sha256:20e736b1e…
Size
6 MB
Last updated
1 day ago
docker pull woozymasta/regdoc