Skip to content

Documentation publishing architecture

Lifecycle: Current. Last verified end to end: 2026-07-17.

Purpose

This page is the canonical reference for how Markdown becomes the production site. Use the publishing runbook for procedures.

flowchart LR
    Git["Forgejo main branch"] --> Job["Forgejo Actions mkdocs job"]
    Job --> Build["mkdocs build --strict"]
    Build --> Publish["CT 101 runner-data publish/site"]
    Publish --> Static["docs-static Caddy :30084"]
    Static --> Ingress["CT 103 Caddy :443"]
    Ingress --> Users["docs.kh3group.com"]

Authoritative build path

Item Current value Evidence
Workflow .forgejo/workflows/docs.yml Repository source
Runner label mkdocs CT 101 runner config, 2026-07-17
Label image squidfunk/mkdocs-material:latest CT 101 runner config
Job container squidfunk/mkdocs-material:9 Workflow
Dependencies Pinned requirements.txt Workflow
Build command mkdocs build --strict Workflow
Runner mount /opt/podman/volumes/forgejo/runner-data:/runner-data Workflow and runner allow-list
Publish path in job /runner-data/publish/kh3-it-infra-docs/site Workflow
Publish path on CT 101 /opt/podman/volumes/forgejo/runner-data/publish/kh3-it-infra-docs/site Live filesystem
Static unit docs-static.service / docs-static.container Live rootless user systemd
Static image docker.io/library/caddy:2-alpine Live Podman
Static mount Publish path to /srv:ro Live Quadlet
Backend 192.168.2.20:30084 to container :8080 Live Quadlet and HTTP check
Ingress docs.kh3group.com -> 192.168.2.20:30084 CT 103 Caddyfile

The runner configuration also allows /opt/podman/volumes/forgejo/runner-ssh; no other bind root is required for this workflow.

The runner daemon must explicitly load its configuration:

forgejo-runner daemon --config /config.yml

Setting only CONFIG_FILE=/config.yml does not make Forgejo Runner load the file. Without the explicit option, container.valid_volumes may be ignored.

Deployment validation

After a main push copies the artifact, the workflow performs three checks:

  1. site/index.html must exist before and after the copy.
  2. docs-static:8080 must return an HTTP status from 200 through 399 on the runner's kh3-backend network.
  3. CT 103 at 192.168.2.3:443 must return an HTTP status from 200 through 399 for the docs.kh3group.com host and Server Name Indication (SNI). The Python default trust store verifies the certificate chain and hostname.

A failed check makes the deployment fail even if the files copied successfully. Inspect CT 101 when the backend check fails. Inspect DNS, certificate issuance, and CT 103 when only the ingress check fails.

Ownership and persistence

The publish directory and its current files were owned by podsvc:podsvc with directory mode 0755 and file mode 0644 on 2026-07-17. The workflow copies new output into the existing directory and applies readable permissions. docs-static mounts the directory read-only; it cannot alter the build.

The generated artifact is rebuildable and is not the source backup. Git and a separate repository backup protect the source. The publish directory is useful for rapid serving recovery and forensic comparison, but it does not replace source control.

Current defect: stale production artifact

On 2026-07-17 all serving layers returned HTTP 200, but the published index.html and HTTP Last-Modified timestamp were 2026-07-02 11:19:46 UTC. The local branch also contains unpublished/divergent work. This means the serving path is healthy while no newer documentation artifact has reached the shared publish directory.

Use the stale publication procedure before changing Caddy.

Drone status

.drone.yml describes a superseded direct copy into CT 103 /opt/caddy/sites/docs.kh3group.com. That path conflicts with the verified architecture and must not be used for current publication. Whether the Drone server/webhook is still enabled for this repository was not safely verified on 2026-07-17. Treat the file as historical pipeline evidence, not an active publication authority.

Official references