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:
site/index.htmlmust exist before and after the copy.docs-static:8080must return an HTTP status from200through399on the runner'skh3-backendnetwork.- CT
103at192.168.2.3:443must return an HTTP status from200through399for thedocs.kh3group.comhost 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.