Configuration of MkDoc
This document describes the complete MkDocs configuration for the Scanframe Wiki documentation service, including Docker setup, PlantUML rendering, PWA manifest, CPU optimization, and custom styling.
Project Structure
The documentation project is organized across two main directories:
| Directory | Purpose | Container Mount |
|---|---|---|
mkdoc/ |
MkDocs configuration, Dockerfile, theme overrides, and static assets | /docs (read-only) |
docroot/ |
Markdown content files (wiki, progress, miscellaneous) | /src (read-only) |
The docroot/ directory contains symlinks or subdirectories for each content section:
docroot/wiki/— Wiki documentation pages.docroot/progress/— Progress tracking pages.docroot/misc/— Miscellaneous content.docroot/website/— Production website content.
Docker Service
Dockerfile (mkdoc/Dockerfile)
The Dockerfile extends the squidfunk/mkdocs-material base image and adds:
- Java runtime and Graphviz — Required for local PlantUML rendering.
- PlantUML JAR — Downloaded to
/opt/plantuml/plantuml.jarfor offline diagram rendering. - Python packages —
mkdocs-awesome-pages-plugin,mkdocs-glightbox,mkdocs-build-plantuml-plugin, andplantuml-markdown. - CPU optimization — The livereload polling interval is patched from
0.5seconds to600seconds (10 minutes) to eliminate idle CPU usage.
Docker Compose (mkdoc/docker-compose.yml)
The service is defined in mkdoc/docker-compose.yml:
- Image: Built from the local Dockerfile.
- Port mapping:
127.0.0.1:8081maps to container port8000. - Volumes: Two read-only mounts:
../mkdoc→/docs(MkDocs configuration and static assets).../docroot→/src(Markdown content directories).
- Restart policy:
unless-stoppedensures the service restarts on container restart. - User: Runs as
www-data(UID 33:33).
Starting the Service
Use the build.sh script from the project root:
| Command | Description |
|---|---|
build |
Build the Docker image and start the service. |
up |
Start the service in the background. |
down |
Stop and remove the container. |
restart |
Restart the running container. |
status |
Show whether the container is running. |
info |
Show Docker image information. |
prune |
Remove unused Docker resources. |
exec |
Execute a command inside the container. |
The site is available at http://127.0.0.1:8081.
Build Management Script (mkdoc/build.sh)
A symlinked script build.sh in the project root provides all Docker Compose commands for the MkDocs service. The actual script
resides in mkdoc/build.sh and uses two directory variables:
script_dir— Resolves to the actual script location (mkdoc/), used for findingdocker-compose.yml.run_dir— Resolves to the directory from which the script was invoked (the symlink's location, i.e., the project root).
Available Commands
| Command | Description |
|---|---|
build |
Build the Docker image and start the service. |
restart |
Restart the running container, triggering a full rebuild. |
up |
Start the service in the background. |
down |
Stop and remove the container. |
status |
Show whether the container is running and display stats using jq. |
info |
Show information about the Docker image. |
prune |
Remove unused Docker resources. |
exec |
Execute a command inside the running container, passing remaining args. |
auto-restart |
Watch documentation files and auto-restart the container on changes. |
help |
Show the help message. |
Status Command
The status command uses jq to parse the NDJSON output from docker compose ps --format json, extracting the .State field to
determine if the container is running.
Auto-Restart Command
The auto-restart command provides event-driven file watching with near-zero CPU usage as an alternative to manual rebuilds:
- Builds watch list — Collects real paths (via
readlink -f) of all entries in${script_dir}/docsand themkdocs.ymlconfiguration file. - Watches for changes — Uses
inotifywaitto monitor formodify,create,delete, andmoveevents across all watched paths. - Debounces — Waits
7seconds after the last change before triggering a restart, so rapid saves trigger only one rebuild. - Restarts container — Executes
docker compose restartto rebuild all pages.
Requires inotify-tools package installed. The command runs in the foreground and can be stopped with Ctrl+C.
Manual Rebuilds
Due to the CPU optimization that disables file polling, automatic live reload is not available by default. Instead, use one of:
./build.sh restart— Restarts the container, forcingmkdocs serveto rebuild all pages from scratch on startup../build.sh auto-restart— Event-driven watching with automatic restarts and near-zero CPU usage../rebuild.sh— Legacy script that restarts the container viadocker compose restart.
MkDocs Configuration (mkdoc/mkdocs.yml)
Site Metadata
| Key | Value | Purpose |
|---|---|---|
site_name |
Scanframe Wiki |
Browser tab title and homepage heading. |
site_description |
Public Wiki of Scanframe |
SEO and social media preview text. |
site_url |
https://wiki.scanframe.com |
Base URL for absolute links and sitemap. |
dev_addr |
0.0.0.0:8000 |
Binds to all interfaces so Docker port mapping works. |
File Watching (Disabled)
The watch section is commented out to avoid high CPU usage from continuous file polling. Manual rebuilds via rebuild.sh are used
instead.
Theme Configuration
The site uses Material for MkDocs with the following settings:
- Custom directory:
overrides— Contains template overrides (e.g., the PWA manifest link). - Favicon:
assets/scanframe.ico. - Logo:
assets/scanframe.png.
Theme Features
| Feature | Purpose |
|---|---|
content.code.copy |
Copy button on code blocks. |
search.suggest |
Autocomplete suggestions in the search box. |
search.highlight |
Highlights search matches on result pages. |
search.share |
Shareable search result URLs. |
navigation.indexes |
Allows landing pages for sections (index.md as section default). |
content.code.annotate |
Code block annotations via HTML comments. |
content.tabs.link |
Syncs code tab selections across pages in a session. |
Color Palette
Two palettes are defined based on the user's OS-level color scheme preference:
- Light mode:
defaultscheme withindigoprimary andslateaccent colors. - Dark mode:
slatescheme withindigoprimary andslateaccent colors.
Each palette includes a toggle button to switch to the other scheme.
Plugins
| Plugin | Purpose |
|---|---|
search |
Built-in search index generation for the site search feature. |
awesome-pages |
Auto-generates navigation from directory structure using _navigate_.yml files. Sections with a single page are not collapsed. |
glightbox |
Lightbox gallery for images — click to view full-size with navigation. |
Navigation Structure (mkdoc/docs/.pages.yml)
The awesome-pages plugin uses .pages.yml to define the navigation order:
nav:
- 🌐 Website: website
- 📢 Introduction: index.md
- 📙 Wiki: wiki
- 📈 Progress: progress
- 📦 Miscellaneous: misc
Each entry maps a display title (with emoji prefix) to a content directory or file. Subdirectories are auto-discovered unless
explicitly overridden with a .pages file in the target subdirectory.
Renaming Subdirectories
To rename a subdirectory in the navigation, create a .pages file in the target subdirectory with a title: field. For example,
docroot/wiki/Configuration/.pages contains:
This overrides the auto-discovered directory name in the navigation sidebar.
Emoji Title Prefixes
All Markdown documents in the content directories have emoji prefixes in their title: frontmatter field:
docroot/progress/— All47files prefixed with🔵.docroot/wiki/— All146files prefixed with🟠.
Extra Configuration
generator: false— Hides the "Powered by MkDocs" footer line.
Custom Stylesheets
stylesheets/extra.css— Custom CSS that overrides theme defaults for font sizes, spacing, colors, and layout adjustments.
Custom JavaScript
| File | Purpose |
|---|---|
javascripts/external-links.js |
Adds external link icons and target="_blank" to outbound links. |
javascripts/hide-elements.js |
Hides header, navigation sidebar, and footer on the production domain (www.scanframe.com) when the path starts with /website. |
Markdown Extensions
| Extension | Purpose |
|---|---|
attr_list |
Allows HTML attributes on Markdown elements (e.g., class, id). |
md_in_html |
Renders Markdown inside HTML blocks (e.g., <div> with Markdown content). |
pymdownx.highlight |
Syntax highlighting for fenced code blocks (Pygments-based) with line number anchors and custom CSS classes per language. |
pymdownx.inlinehilite |
Inline code highlighting with backticks and a language (e.g., `code { .python }). |
pymdownx.snippets |
Includes external files via {{ !path/to/file }} syntax. |
pymdownx.superfences |
Extended fenced code blocks with highlighting, line numbers, and titles. |
pymdownx.tabbed |
Tabbed content blocks for showing multiple code variants (alternate CSS style). |
PlantUML Diagram Rendering
Overview
PlantUML diagrams are rendered offline using a local Java-based PlantUML JAR installed in the Docker container. No external network access is required.
Configuration
The plantuml_markdown extension is configured in mkdocs.yml:
- plantuml_markdown:
plantuml_cmd: 'java -Dplantuml.include.path=/src/progress:/src/wiki:/src/misc -jar /opt/plantuml/plantuml.jar'
format: 'svg'
plantuml_cmd: Command to invoke PlantUML locally. The-Dplantuml.include.pathJVM argument tells PlantUML where to resolve!includedirectives. The paths match the Docker volume mounts.format: Output format issvg(recommended for scalability).
Supported Patterns
Two PlantUML patterns are supported:
-
Inline diagrams — Diagram code written directly in a fenced code block:
<img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjEiIGRhdGEtZGlhZ3JhbS10eXBlPSJTRVFVRU5DRSIgc3R5bGU9IndpZHRoOjExN3B4O2hlaWdodDoxMzJweDtiYWNrZ3JvdW5kOiNGRkZGRkY7IiB3aWR0aD0iMTE3cHgiIGhlaWdodD0iMTMycHgiIHZpZXdCb3g9IjAgMCAxMTcgMTMyIiB6b29tQW5kUGFuPSJtYWduaWZ5IiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJub25lIiBjb250ZW50U3R5bGVUeXBlPSJ0ZXh0L2NzcyI+PD9wbGFudHVtbCAxLjIwMjYuOT8+PGRlZnMvPjxnIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmciPjxnIGNsYXNzPSJwYXJ0aWNpcGFudC1saWZlbGluZSIgZGF0YS1xdWFsaWZpZWQtbmFtZT0iQWxpY2UiIGRhdGEtZW50aXR5LXVpZD0icGFydDEiIGlkPSJwYXJ0MS1saWZlbGluZSIgZGF0YS1zb3VyY2UtbGluZT0iMiI+PGc+PHRpdGxlPkFsaWNlPC90aXRsZT48cmVjdCB4PSIyOC41MTUiIHk9IjQxLjgiIHdpZHRoPSI4IiBoZWlnaHQ9IjQ3LjYiIGZpbGw9IiMwMDAiIGZpbGwtb3BhY2l0eT0iMCIvPjxsaW5lIHgxPSIzMi4wMTUiIHkxPSI0MS44IiB4Mj0iMzIuMDE1IiB5Mj0iODkuNCIgc3R5bGU9InN0cm9rZTojMTgxODE4O3N0cm9rZS13aWR0aDowLjU7c3Ryb2tlLWRhc2hhcnJheTo1LDU7Ii8+PC9nPjwvZz48ZyBjbGFzcz0icGFydGljaXBhbnQtbGlmZWxpbmUiIGRhdGEtcXVhbGlmaWVkLW5hbWU9IkJvYiIgZGF0YS1lbnRpdHktdWlkPSJwYXJ0MiIgaWQ9InBhcnQyLWxpZmVsaW5lIiBkYXRhLXNvdXJjZS1saW5lPSIyIj48Zz48dGl0bGU+Qm9iPC90aXRsZT48cmVjdCB4PSI4My4zNTEiIHk9IjQxLjgiIHdpZHRoPSI4IiBoZWlnaHQ9IjQ3LjYiIGZpbGw9IiMwMDAiIGZpbGwtb3BhY2l0eT0iMCIvPjxsaW5lIHgxPSI4Ni44NTEiIHkxPSI0MS44IiB4Mj0iODYuODUxIiB5Mj0iODkuNCIgc3R5bGU9InN0cm9rZTojMTgxODE4O3N0cm9rZS13aWR0aDowLjU7c3Ryb2tlLWRhc2hhcnJheTo1LDU7Ii8+PC9nPjwvZz48ZyBjbGFzcz0icGFydGljaXBhbnQgcGFydGljaXBhbnQtaGVhZCIgZGF0YS1xdWFsaWZpZWQtbmFtZT0iQWxpY2UiIGRhdGEtZW50aXR5LXVpZD0icGFydDEiIGlkPSJwYXJ0MS1oZWFkIiBkYXRhLXNvdXJjZS1saW5lPSIyIj48cmVjdCB4PSIxMCIgeT0iMTAiIHdpZHRoPSI0NC4wMyIgaGVpZ2h0PSIzMC44IiBmaWxsPSIjRTJFMkYwIiBzdHlsZT0ic3Ryb2tlOiMxODE4MTg7c3Ryb2tlLXdpZHRoOjAuNTsiIHJ4PSIyLjUiIHJ5PSIyLjUiLz48dGV4dCB4PSIxNyIgeT0iMzAuNzYyIiBmaWxsPSIjMDAwIiBmb250LXNpemU9IjE0IiB0ZXh0TGVuZ3RoPSIzMC4wMyI+QWxpY2U8L3RleHQ+PC9nPjxnIGNsYXNzPSJwYXJ0aWNpcGFudCBwYXJ0aWNpcGFudC1oZWFkIiBkYXRhLXF1YWxpZmllZC1uYW1lPSJCb2IiIGRhdGEtZW50aXR5LXVpZD0icGFydDIiIGlkPSJwYXJ0Mi1oZWFkIiBkYXRhLXNvdXJjZS1saW5lPSIyIj48cmVjdCB4PSI2Ny40ODIiIHk9IjEwIiB3aWR0aD0iMzguNzM4IiBoZWlnaHQ9IjMwLjgiIGZpbGw9IiNFMkUyRjAiIHN0eWxlPSJzdHJva2U6IzE4MTgxODtzdHJva2Utd2lkdGg6MC41OyIgcng9IjIuNSIgcnk9IjIuNSIvPjx0ZXh0IHg9Ijc0LjQ4MiIgeT0iMzAuNzYyIiBmaWxsPSIjMDAwIiBmb250LXNpemU9IjE0IiB0ZXh0TGVuZ3RoPSIyNC43MzgiPkJvYjwvdGV4dD48L2c+PGcgY2xhc3M9InBhcnRpY2lwYW50IHBhcnRpY2lwYW50LXRhaWwiIGRhdGEtcXVhbGlmaWVkLW5hbWU9IkFsaWNlIiBkYXRhLWVudGl0eS11aWQ9InBhcnQxIiBpZD0icGFydDEtdGFpbCIgZGF0YS1zb3VyY2UtbGluZT0iMiI+PHJlY3QgeD0iMTAiIHk9Ijg5LjQiIHdpZHRoPSI0NC4wMyIgaGVpZ2h0PSIzMC44IiBmaWxsPSIjRTJFMkYwIiBzdHlsZT0ic3Ryb2tlOiMxODE4MTg7c3Ryb2tlLXdpZHRoOjAuNTsiIHJ4PSIyLjUiIHJ5PSIyLjUiLz48dGV4dCB4PSIxNyIgeT0iMTEwLjE2MiIgZmlsbD0iIzAwMCIgZm9udC1zaXplPSIxNCIgdGV4dExlbmd0aD0iMzAuMDMiPkFsaWNlPC90ZXh0PjwvZz48ZyBjbGFzcz0icGFydGljaXBhbnQgcGFydGljaXBhbnQtdGFpbCIgZGF0YS1xdWFsaWZpZWQtbmFtZT0iQm9iIiBkYXRhLWVudGl0eS11aWQ9InBhcnQyIiBpZD0icGFydDItdGFpbCIgZGF0YS1zb3VyY2UtbGluZT0iMiI+PHJlY3QgeD0iNjcuNDgyIiB5PSI4OS40IiB3aWR0aD0iMzguNzM4IiBoZWlnaHQ9IjMwLjgiIGZpbGw9IiNFMkUyRjAiIHN0eWxlPSJzdHJva2U6IzE4MTgxODtzdHJva2Utd2lkdGg6MC41OyIgcng9IjIuNSIgcnk9IjIuNSIvPjx0ZXh0IHg9Ijc0LjQ4MiIgeT0iMTEwLjE2MiIgZmlsbD0iIzAwMCIgZm9udC1zaXplPSIxNCIgdGV4dExlbmd0aD0iMjQuNzM4Ij5Cb2I8L3RleHQ+PC9nPjxnIGNsYXNzPSJtZXNzYWdlIiBkYXRhLWVudGl0eS0xPSJwYXJ0MSIgZGF0YS1lbnRpdHktMj0icGFydDIiIGlkPSJtc2cxIiBkYXRhLXNvdXJjZS1saW5lPSIyIj48cG9seWdvbiBwb2ludHM9Ijc0Ljg1MSw2Ny40LDg0Ljg1MSw3MS40LDc0Ljg1MSw3NS40LDc4Ljg1MSw3MS40IiBmaWxsPSIjMTgxODE4IiBzdHlsZT0ic3Ryb2tlOiMxODE4MTg7c3Ryb2tlLXdpZHRoOjE7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO3N0cm9rZS1taXRlcmxpbWl0OjEwOyIvPjxsaW5lIHgxPSIzMi4wMTUiIHkxPSI3MS40IiB4Mj0iODAuODUxIiB5Mj0iNzEuNCIgc3R5bGU9InN0cm9rZTojMTgxODE4O3N0cm9rZS13aWR0aDoxOyIvPjx0ZXh0IHg9IjM5LjAxNSIgeT0iNjYuNTc5IiBmaWxsPSIjMDAwIiBmb250LXNpemU9IjEzIiB0ZXh0TGVuZ3RoPSIzMC44MzYiPkhlbGxvPC90ZXh0PjwvZz48P3BsYW50dW1sLXNyYyBTeXA5SjR2THFCTEpTQ2ZGaWJCbUlDdDlvVVRJVzA4a0JhQ3o4bUNaQ3ByQjAwMDA/PjwvZz48L3N2Zz4=" class="uml" alt="uml diagram" title="" /> -
Include diagrams — Reference to an external
.pumlfile:<img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjEiIHN0eWxlPSJ3aWR0aDo1NDBweDtoZWlnaHQ6MzM0cHg7YmFja2dyb3VuZDojRkZGRkZGOyIgd2lkdGg9IjU0MHB4IiBoZWlnaHQ9IjMzNHB4IiB2aWV3Qm94PSIwIDAgNTQwIDMzNCIgem9vbUFuZFBhbj0ibWFnbmlmeSIgcHJlc2VydmVBc3BlY3RSYXRpbz0ibm9uZSIgY29udGVudFN0eWxlVHlwZT0idGV4dC9jc3MiPjw/cGxhbnR1bWwgMS4yMDI2Ljk/PjxkZWZzLz48ZyBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nIj48cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iNTM5LjE1MSIgaGVpZ2h0PSIyMTEuNjAxIiBmaWxsPSIjRkZGIiBzdHlsZT0ic3Ryb2tlOiNGRkY7c3Ryb2tlLXdpZHRoOjE7Ii8+PHRleHQgeD0iNSIgeT0iMTYuNzk2IiBmaWxsPSIjMDAwIiBmb250LXNpemU9IjEyIiB0ZXh0TGVuZ3RoPSIxMjkuMjQ3IiBmb250LXdlaWdodD0iNzAwIj5XZWxjb21lIHRvIFBsYW50VU1MITwvdGV4dD48dGV4dCB4PSI1IiB5PSIzMS4xOTYiIGZpbGw9IiMwMDAiIGZvbnQtc2l6ZT0iMTIiPsKgPC90ZXh0Pjx0ZXh0IHg9IjUiIHk9IjQ1LjU5NiIgZmlsbD0iIzAwMCIgZm9udC1zaXplPSIxMiIgdGV4dExlbmd0aD0iMjQyLjIxIj5Zb3UgY2FuIHN0YXJ0IHdpdGggYSBzaW1wbGUgVU1MIERpYWdyYW0gbGlrZTo8L3RleHQ+PHRleHQgeD0iNSIgeT0iNTkuOTk2IiBmaWxsPSIjMDAwIiBmb250LXNpemU9IjEyIj7CoDwvdGV4dD48dGV4dCB4PSI1IiB5PSI3NC4zOTYiIGZpbGw9IiMwMDAiIGZvbnQtc2l6ZT0iMTIiIHRleHRMZW5ndGg9IjkzLjE2OSIgZm9udC1mYW1pbHk9Im1vbm9zcGFjZSI+Qm9iLT5BbGljZTrCoEhlbGxvPC90ZXh0Pjx0ZXh0IHg9IjUiIHk9Ijg4Ljc5NyIgZmlsbD0iIzAwMCIgZm9udC1zaXplPSIxMiI+wqA8L3RleHQ+PHRleHQgeD0iNSIgeT0iMTAzLjE5NyIgZmlsbD0iIzAwMCIgZm9udC1zaXplPSIxMiIgdGV4dExlbmd0aD0iMTMuNjQ0Ij5PcjwvdGV4dD48dGV4dCB4PSI1IiB5PSIxMTcuNTk3IiBmaWxsPSIjMDAwIiBmb250LXNpemU9IjEyIj7CoDwvdGV4dD48dGV4dCB4PSI1IiB5PSIxMzEuOTk3IiBmaWxsPSIjMDAwIiBmb250LXNpemU9IjEyIiB0ZXh0TGVuZ3RoPSI3NC43OTciIGZvbnQtZmFtaWx5PSJtb25vc3BhY2UiPmNsYXNzwqBFeGFtcGxlPC90ZXh0Pjx0ZXh0IHg9IjUiIHk9IjE0Ni4zOTciIGZpbGw9IiMwMDAiIGZvbnQtc2l6ZT0iMTIiPsKgPC90ZXh0Pjx0ZXh0IHg9IjUiIHk9IjE2MC43OTciIGZpbGw9IiMwMDAiIGZvbnQtc2l6ZT0iMTIiIHRleHRMZW5ndGg9IjMwMy4xOTQiPllvdSB3aWxsIGZpbmQgbW9yZSBpbmZvcm1hdGlvbiBhYm91dCBQbGFudFVNTCBzeW50YXggb248L3RleHQ+PHRleHQgeD0iMzEwLjgzNCIgeT0iMTYwLjc5NyIgZmlsbD0iIzAwMCIgZm9udC1zaXplPSIxMiIgdGV4dExlbmd0aD0iMTEzLjMxNyIgdGV4dC1kZWNvcmF0aW9uPSJ1bmRlcmxpbmUiPmh0dHBzOi8vcGxhbnR1bWwuY29tPC90ZXh0Pjx0ZXh0IHg9IjUiIHk9IjE3NS4xOTciIGZpbGw9IiMwMDAiIGZvbnQtc2l6ZT0iMTIiPsKgPC90ZXh0Pjx0ZXh0IHg9IjUiIHk9IjE4OS41OTciIGZpbGw9IiMwMDAiIGZvbnQtc2l6ZT0iMTIiIHRleHRMZW5ndGg9IjkyLjIzMyI+KERldGFpbHMgYnkgdHlwaW5nPC90ZXh0Pjx0ZXh0IHg9Ijk5Ljg3MyIgeT0iMTg5LjU5NyIgZmlsbD0iIzAwMCIgZm9udC1zaXplPSIxMiIgdGV4dExlbmd0aD0iMzYuOTM2IiBmb250LWZhbWlseT0ibW9ub3NwYWNlIj5saWNlbnNlPC90ZXh0Pjx0ZXh0IHg9IjEzOS40NDkiIHk9IjE4OS41OTciIGZpbGw9IiMwMDAiIGZvbnQtc2l6ZT0iMTIiIHRleHRMZW5ndGg9IjQ5LjI0OCI+a2V5d29yZCk8L3RleHQ+PHRleHQgeD0iNSIgeT0iMjAzLjk5NyIgZmlsbD0iIzAwMCIgZm9udC1zaXplPSIxMiI+wqA8L3RleHQ+PGltYWdlIHdpZHRoPSI4MCIgaGVpZ2h0PSI3MSIgeD0iNDUzLjE1MSIgeT0iNiIgeGxpbms6aHJlZj0iZGF0YTppbWFnZS9wbmc7YmFzZTY0LGlWQk9SdzBLR2dvQUFBQU5TVWhFVWdBQUFGQUFBQUJIQ0FNQUFBQ25IREM4QUFBREFGQk1WRVgvLy8rQnBidkQzdS9mN3ZmUHo4K256K2cza3N1WkVEa1dBRHIvdWpsdnNkcFRvZExDd3NPTHdPRWJnOFQvL3Y2cHFhbVpFVG5MeXN2VDA5VG81K2pxTFM0VkFUajkvZjI3dTd2RHc4TVRmc0srdjcvajQrU093ZUpwcmRqTDQvS3hzYkVyaThqazhQZ1hhNTdjMjl1VUVUZXoxdXRRb05KMXROdEVtYzljcHRXQnV0NmF5T1cvM082WUVEalk2dlVmaGNYQndjSHM3T3p3OS92ODN0Nzl1VG43Ky92WDE5ZWhvYUh4OGZHMnRMWEh4OGZ1Vmxmck5UWWFCam4vdlVINStmbWtvNldYbDVmLytmbjk1dWIrOVBUKzhQRDNxS2lkbWFNeElVcm5xRFdjRmo3bjVlWDA5UFR1N3UyU2twS2ZuWjZycTZ6MGxKVG00dHpKckhVZkRUMzM5L2VGR0RuNnRqY3JFek92cmJCdVozbDRlb21TTVM3SHo5U21wcWFzdThTRmlZdVFFVGZhMmR1MW02T09NMDUxSjBOV1RXTDV4c2U0dUxqNnpjMzNyNi80dmI3eWRIWHFMaS83MmRudFRFM3RSMGlqYm41alczQyt1OE5BTlZRckcwWEZ1YnFSVkdaemE0R1FHVDJWYVM3YTBOUEp0STZJSmovenN6cmZxa1hYenIvSHc4Y2dDMExVcWx0T1EySnNTQzViVEdqT3BGYWZTaTIraURHdHl0MTRtN1RKaFRDUEpVU3dvSUcvcWEraWtIaUlncENSSGpJOWo4S1hibmFRSDBGTGY2RkdQbHAwY25ZeUtFMnJnNDlXajdkRlhIbWN3dHJwNnV1K3hNanZZV0x2VzF6OTdlM3NQRDNzUDBEdVVsUDcwOVBZemREUG16cVpXV3lTUlZ5Z2ZISGQxc21Xa1orUGduaWJFVHFzbDI2MHE1Zkx1cHBKTEMvZHBqMkhQRkxUdEh2L3VqclR3Snk1czZpV2pJaTBpRDNqb1RUQ21VM3RyanV2WlMyemxGdmpxMEhTeUxiWXAwcVJ0czEwWEd4RUtDOXlQVkJRWklPUGRsTnZNazJOZVlOcVJsdWp0Y0NWcnIzajVlWTFnSzlRbjlHTHA3bGJlSlhSeHJDa2laRGFvenl1a29KK0gwQjNyYytPWXkraXBySmtZWGRobXI2ZWUwYWZzNzl4cTlCaWtyR0tqSTNGb21FaUxGTzBheTZuZkltNHBaWFJqVEZ1aFpTWldtMHpjWnlKTjBLTlRrNGVlYk5mZElHR2xaOTNoNWtjRUQ1bk9WWXlSV2J6aUlrOWRKNGtlckU1Wll4UlVuTEcxZDdwOFBUdlkyUHRUay8xbloxeWNWL2JBQUFGeDBsRVFWUjRYdTFZZTFCVVZSZy9lM2RobDRjc0t5eHFQbEJKUVZFVEYyaWNBWTEzR29na0VGcWl6cVJScG9CcFRDSWFwYVVPaFdOalVkT01TQnBxSllaVGc0S2lnRktBRHpTSVZaNHFENVZGbHVYaExndmJQZWUrcjQ5Ujd2MnozK3pjYzc3Zk9mUGI3enZmUGQ4NXV3Q0lod1Q0d1Bpc0FOd1BBdUlLSG5iSEgxSStLd0FYREExOFNoRFc1OENuZUNFblhsOEpHd21mSHlsQ1BTdmxaL0ZXeGg4WUlSSXhUV2taQUVvZmtUejhzTnE3dEFxRTJnT0RPQjZHWVpvYlZhSDIwU1ZaNG9RY2RtWFYwTDdjNGI4T0Z2SkhSb1l3ZFZJb3lQWDNneHRGREErVjhlNm1NOHBCb0NqaWo0d1FpV28vQUx6Vi9rcGtpZkJpMTNwNHdhWnVHckpFRUN5Y2l4Y0VWU2JZZ0Z3VW96ZzQvcVNyYU94clA3TzhFb2dqMkhEeHlNV216cGs2ODIzK3lJamh2UjdmTE9wRHNDdUdod0MwV2Zia2pRcThBME1XQzBFZ0lZelBDWU15bU04SXdjdno2SzRJV3c4c2tUcmdxM2lQTUlRbnhkZktydnllZHB5dmJTc3lCUXQ2emNINk94WG1qcVl4ZzlQYmdYQkJ2ZUs2YVJBWE5FbDFTc2MraHo3QmdudHlvaGIvZ2dRbE5ycmR6Zkl1b1lLU2I3OTA2N1ByUm9MbUdvMjVTZWdoSlJuMXlNclNiUFRwdFphb0Jsd3RsV2JCSHNweGlXK21XOCs0NmVyZVZuTVhHeGJGUSttam9maUhaVHJjbEpuNUUxNFVFZ2RyTzl0UlZwaDBIREpsWWxSc0x2NFhGQTdSQkExa0s1b2dCYjdndG4wOEF2eFd5bWMyYitZekxIQzNubk9DTkh2ZGF5VXNacG5ud25QcUdmK3lHS2NQS2x2OTJ2dEpDMjA5ekd5UldKbWd5ZDE2c1NHR1QvQW1NMnJkS1pLWkh4d0N6N0tVb2RMekpPTzh0aDRPQnF4ZG8wYzJ0ZlVrTnIzUWxKa1pRV1dNQlAwdXdCSHZtUVFiM3pSVkNNbnNNTzZBVGF6ZjNmMGtremEwSFRhUENkSWhweVRrbnFQNjFUZTFlVjJnUnByMUZVbjR4dHhSVDcycERIaGowOS9VbkJMaml2NzJwNGQ4RkV0bExpYkJkN1FnMHhLNStYZlM5cTJBenhTVkhQbk5JTUJTOEJRUHc4TjFXNGdsUVdqMG10cDQrblNIaHZLWU9NNEdMNnZIMWpHVGNEUTNwMTk3OUFRUEk1YWdYRURZd3VTNWF3a25KNzNySFU1NlJ5TEFyNkFDcE81aU1SR3ZwM1h5a2pJbWdzNEZnbTIvN1VSYzhkaGJlRDlqZGhZVk5vbjBtZEZjQWpUSVhibUM3NVZybVZIY1ErSkRJWGJ1Tm1ZVVlsWm9ZeDZYY1dqS1RXYXZZWGQxV3pVOU9FaDl3TEZmRVZGemE1UEVoVmhEQXZmTHg2MGNXTTNlUE1hOWh5Wkc1RE5yNkRiUWlDVWNZVTBBS0d6R3lZeGxrZit3eG5Dayt3ZHdDYkEvVGtXSDdEYmdvcThkY21LTnNzUVEzRi9KbU14bElqL2Z6by83czlPSXdVTWViYll6YmgyekxmazdlaEJHeklhdTZPZU8reHpHS2xCUndDR0FYTmFDRmdZYlJvS0craE1UZk1mZjRNNmgwTFl4cG12LzkyeW05dEl4YzR1Unphd3luQ0VXR2h1bTdvZGY5Lzc1dmg4emcwYktpalg1QU14aE1hY3NLOHBCeGFKaWh0blFxcjFLRzh5Rk16cS9kdXRKMmlLd05DYlhBN1lhbXNtMlhuMFh0Z3Rvd1kzWHk4cnBZYzROTnFvbjhlTTQ5aytOdHpVNTVHdE1sZUdBa05SNm9sZERNaEgrZTlFWDBKQitVWUt2NFdnTFpyTENqSFY3d255WWx6Slp2WVY2QTZkMXc2eEVwaDg0MlVVeVRrakdJYnp3UkEvSklPQkpLU3Q4OHlnbHFORDFoNlRwaVFxUVY5OXdtSjdua25RY2dJSUhxZDAwRXdTemd1ZGlnR1lRVUhFNHFGTWNHSmIyMmtwN2xOb3A4cDZ6blN0dmc1UzJlMVRWUnRpNTZ3L3pqOGZaektMaURhMzE3TXFCUUZicytKM2RjYlNnbytINEQwa0xPb2tKODIvTlI4S1picDllSVpnbGx3S1I4RWMxZWpJWDgyN1BPay8wNkhwWWZmR2Q1ZGtvWkpYTXFEQVZYZDFOYmhhdmx4NE95dUFpUGN5QUYzSWNrOGZhWEF1RTVXVG9BcGtNeHlrTDFQYk5SSjkxU0RrWE95eW1QRlIxVU1sdzlHalJQS0NyUHNSU3ZVbmJ6YmtFTGoyUFNTUmtRTGlIMUF2Uk9UdXRscHJ5TEhRQW9DRCtDMkJCUDRucE13ZDl6c0lnemdhRHNLK2JjdG1WdzNoZXRhaTV0OVJTNFBFcXViNFFuQ3Z4dGVHek9aeVFYeGpzY3hraEhpc1dLTWk3MitUMHAxZHhHZUdZa0cwZXorZWVHN0luL24vb05PZXhDOWZ6SWlMdlB4VnRLcDg0aXphK0FBQUFBRWxGVGtTdVFtQ0MiLz48cmVjdCB4PSIwIiB5PSIyMTEuNjAxIiB3aWR0aD0iNTM5LjE1MSIgaGVpZ2h0PSIxMjEuNCIgZmlsbD0iIzAwMCIgc3R5bGU9InN0cm9rZTojMDAwO3N0cm9rZS13aWR0aDoxOyIvPjx0ZXh0IHg9IjUiIHk9IjIyOC42MDEiIGZpbGw9IiMzM0ZGMDIiIGZvbnQtc2l6ZT0iMTIiIHRleHRMZW5ndGg9IjM2MS41OCIgZm9udC13ZWlnaHQ9IjcwMCIgZm9udC1zdHlsZT0iaXRhbGljIj5QbGFudFVNTCB2ZXJzaW9uIDEuMjAyNi45IC8gMThkMGFhZSBbMjAyNi0xMC0xNyAwODoyNToxMCBVVENdPC90ZXh0PjxyZWN0IHg9IjUiIHk9IjIzOS4wMDIiIHdpZHRoPSIxNDAuNjEiIGhlaWdodD0iMjEuOCIgZmlsbD0iIzMzRkYwMiIgc3R5bGU9InN0cm9rZTojMzNGRjAyO3N0cm9rZS13aWR0aDoxOyIvPjx0ZXh0IHg9IjYiIHk9IjI1NC4wMDIiIGZpbGw9IiMwMDAiIGZvbnQtc2l6ZT0iMTQiIHRleHRMZW5ndGg9IjEzOC42MSIgZm9udC13ZWlnaHQ9IjcwMCI+W0Zyb20gc3RyaW5nIChsaW5lIDIpIF08L3RleHQ+PHRleHQgeD0iNSIgeT0iMjc0LjgwMSIgZmlsbD0iIzMzRkYwMiIgZm9udC1zaXplPSIxNCIgZm9udC13ZWlnaHQ9IjcwMCI+wqA8L3RleHQ+PHRleHQgeD0iNSIgeT0iMjkxLjYwMSIgZmlsbD0iIzMzRkYwMiIgZm9udC1zaXplPSIxNCIgdGV4dExlbmd0aD0iNzEuMDk1IiBmb250LXdlaWdodD0iNzAwIj5Ac3RhcnR1bWw8L3RleHQ+PHRleHQgeD0iMjkuNjQiIHk9IjMwOC40MDEiIGZpbGw9IiMzM0ZGMDIiIGZvbnQtc2l6ZT0iMTQiIHRleHRMZW5ndGg9IjIwMS43MTEiIGZvbnQtd2VpZ2h0PSI3MDAiIHRleHQtZGVjb3JhdGlvbj0id2F2eSB1bmRlcmxpbmUiPiFpbmNsdWRlIHBhdGgvdG8vZGlhZ3JhbS5wdW1sPC90ZXh0Pjx0ZXh0IHg9IjguMDgiIHk9IjMyNS4yMDEiIGZpbGw9IiNGMDAiIGZvbnQtc2l6ZT0iMTQiIHRleHRMZW5ndGg9IjI0NS44ODUiIGZvbnQtd2VpZ2h0PSI3MDAiPmNhbm5vdCBpbmNsdWRlIHBhdGgvdG8vZGlhZ3JhbS5wdW1sPC90ZXh0Pjw/cGxhbnR1bWwtc3JjIEtyMjAwQ05DbEVJU3FmSEtYT0I0YVdwemFkcHpiQ3A0ekFCNE5CczJxam1TQll2M0ZJQzM4cEN6SW0wMD8+PC9nPjwvc3ZnPg==" class="uml" alt="uml diagram" title="" />
Docker Dependencies
The Dockerfile installs the following packages for PlantUML:
openjdk17-jre— Java runtime for executing the PlantUML JAR.graphviz— Required for PlantUML's!includeand certain diagram types.- PlantUML JAR — Downloaded from the official GitHub releases to
/opt/plantuml/plantuml.jar.
Disabling PlantUML
To disable PlantUML rendering, comment out the plantuml_markdown block in mkdocs.yml and remove the PlantUML-related packages
from the Dockerfile.
PWA (Progressive Web App) Manifest
Overview
The site is installable as a Chrome Progressive Web App (PWA) that launches in a standalone window without browser chrome (address bar, tabs, etc.).
Manifest File (mkdoc/docs/manifest.json)
{
"name": "Scanframe Wiki",
"short_name": "Scanframe",
"start_url": "/",
"display": "standalone",
"theme_color": "#1e3c72",
"background_color": "#1e3c72",
"icons": [
{
"src": "assets/icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "assets/icon-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any maskable"
}
]
}
display: standalone— Removes the browser's address bar, tabs, and navigation controls.theme_color— Set to#1e3c72to match the site's header gradient.- Icons — Two sizes generated from the existing 599×599 logo: 192×192 (home screen) and 512×512 (Chrome App installation).
Template Override (mkdoc/overrides/main.html)
A template override injects the manifest link into the HTML <head>:
{% extends "base.html" %}
{% block extrahead %}
{{ super() }}
<link rel="manifest" href="{{ 'manifest.json' | url }}" crossorigin="use-credentials"/>
{% endblock %}
The custom_dir: overrides setting in mkdocs.yml tells Material for MkDocs to use this template.
Custom CSS (mkdoc/docs/stylesheets/extra.css)
Font and Spacing Adjustments
- Root font size: Reduced from Material's default
125%to100%. - Body text:
0.9remwith1.5line height (default is1.6). - Navigation:
0.9remfont size with1.1line height. - Grid max width: Increased from
61remto100remfor wider content areas.
Sidebar Customization
- Width: Set to
24remon large screens. - Padding: Left padding removed from the sidebar inner container.
- Title wrapping: Long titles wrap instead of showing ellipsis (
...). - Chevron icons: Enlarged to
2.5emwith vertical centering (align-self: center) for better visibility.
Color Overrides
- Header: Dark blue gradient from
#1e3c72to#2a5298. - Footer meta: Reverse gradient from
#2a5298to#1e3c72. - Light mode background: Page background set to
#efefef(light gray) instead of white. - Light mode code blocks: Code background set to
#e0e0e0to contrast with the gray page background.
Production Domain Styling (Hide Chrome)
When served from www.scanframe.com under the /website path, the following elements are hidden:
.md-header— Site header (navigation bar)..md-nav--primary— Navigation sidebar content..md-footer— Site footer..md-sidebar--primary— Sidebar container width collapsed to0so main content fills the full page.
The hide-chrome class is added to <body> by hide-elements.js when both conditions are met: hostname is www.scanframe.com
AND the path starts with /website.
Table Styling
- Vertical borders: Added
1pxright border to all table cells using the theme's--md-default-fg-color--lightestcolor variable. - Clean edges: The right border is removed from the last cell in each row so the table edge stays clean.
- Font size: Table font size reset from theme default to inherit from parent.
Custom JavaScript (mkdoc/docs/javascripts/hide-elements.js)
The script detects the current domain and path, then adds the hide-chrome class to <body> when both conditions are met:
location.hostnameiswww.scanframe.com.location.pathnamestarts with/website.
The script uses Material's document$ subscription pattern (when available) to run on every SPA navigation, ensuring the class
persists across page changes. On non-matching hosts or paths, the script does nothing.
CPU Optimization
Problem
The default MkDocs livereload module polls for file changes every 0.5 seconds, causing approximately 5.5% idle CPU usage even
when no files are being edited.
Solution
The polling interval is patched in the Dockerfile from 0.5 seconds to 600 seconds (10 minutes):
RUN sed -i 's/polling_interval: float = 0\.5/polling_interval: float = 600/' \
/usr/local/lib/python3.11/site-packages/mkdocs/livereload/__init__.py
This effectively stops the constant file system polling while keeping the server running. The idle CPU usage drops from 5.5% to
approximately 0.05%.
Trade-off
With polling disabled, automatic live reload is no longer functional. The watch section in mkdocs.yml is commented out, and
manual rebuilds via build.sh restart or event-driven watching via build.sh auto-restart are used instead.
Alternative: Event-Driven Watching
The auto-restart command in build.sh provides automatic rebuilds with near-zero CPU usage by using Linux's inotify kernel
event system instead of polling. This gives the convenience of automatic rebuilds without the CPU overhead.
Icon Assets
| File | Size | Purpose |
|---|---|---|
assets/scanframe.png |
100×100 | Site logo in the header. |
assets/scanframe-large.png |
599×599 | Source image for PWA icons. |
assets/scanframe.ico |
— | Browser favicon. |
assets/icon-192.png |
192×192 | PWA home screen icon (generated from large logo). |
assets/icon-512.png |
512×512 | PWA installation icon (generated from large logo). |
Enabling and Disabling Features
To Enable File Watching
- Uncomment the
watchsection inmkdoc/mkdocs.yml. - Rebuild the Docker image:
./build.sh build.
To Use Event-Driven Auto-Restart
- Install
inotify-tools:apt-get install inotify-tools. - Run
./build.sh auto-restartin the foreground. - Press Ctrl+C to stop watching.
To Disable PlantUML
- Comment out the
plantuml_markdownblock inmkdoc/mkdocs.yml. - Remove the Java, Graphviz, and PlantUML JAR installation steps from
mkdoc/Dockerfile. - Rebuild the Docker image.
To Change Colors
Edit the primary and accent values under the palette section in mkdocs.yml. See
the Material for MkDocs color documentation for available
colors.
To Add a Navigation Section
Edit mkdoc/docs/_navigate_.yml and add a new entry with an emoji prefix and the content directory path.
To Rename a Subdirectory in Navigation
Create a _navigate_.yml file in the target subdirectory with a title: field, for example:
This overrides the auto-discovered directory name in the navigation sidebar.