<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Fabien</title>
    <description>The latest articles on DEV Community by Fabien (@fabzh).</description>
    <link>https://dev.to/fabzh</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4235522%2Fc68baff6-ea4b-4f82-9005-9ea641002d2f.jpeg</url>
      <title>DEV Community: Fabien</title>
      <link>https://dev.to/fabzh</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/fabzh"/>
    <language>en</language>
    <item>
      <title>How and why I built my open-source documentation app that runs in a single Docker container 😁</title>
      <dc:creator>Fabien</dc:creator>
      <pubDate>Mon, 09 Nov 2026 19:05:50 +0000</pubDate>
      <link>https://dev.to/fabzh/how-and-why-i-built-my-open-source-documentation-app-that-runs-in-a-single-docker-container-4kfc</link>
      <guid>https://dev.to/fabzh/how-and-why-i-built-my-open-source-documentation-app-that-runs-in-a-single-docker-container-4kfc</guid>
      <description>&lt;p&gt;I'm a freelancer and solopreneur, and I run two products: a SaaS for CrossFit competitions, and Kerflow, an open-source tool to find local leads.&lt;/p&gt;

&lt;p&gt;Two products, two documentation sites to maintain. On my own.&lt;/p&gt;

&lt;p&gt;And every time, I ended up with the same setup.&lt;br&gt;
A Node project, a framework, a build step, a CI pipeline… just to show some Markdown.&lt;/p&gt;

&lt;p&gt;So I built &lt;a href="https://tidydoc.dev" rel="noopener noreferrer"&gt;tidydoc&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A folder of Markdown files, one Docker container, and you are done.&lt;/p&gt;

&lt;p&gt;Today, I edit my docs with Claude Code (or by hand), commit, and push to GitHub.&lt;/p&gt;

&lt;p&gt;A few seconds later, the site is up to date. No rebuild, no container restart.&lt;/p&gt;

&lt;p&gt;A few developers asked me about it.&lt;/p&gt;

&lt;p&gt;So today, I will tell you why I built it, and how it works under the hood.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmml5fua1u2pu6ra1xyzh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmml5fua1u2pu6ra1xyzh.png" alt="TidyDoc" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Docs should not need a build pipeline
&lt;/h2&gt;

&lt;p&gt;Let me explain.&lt;/p&gt;

&lt;p&gt;You have two main options to ship docs for your product today.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A static site generator. You install Node, add a framework, write a config file, run a build, and deploy the output.&lt;/li&gt;
&lt;li&gt;A hosted docs platform. Great experience, but you pay per seat and your docs live outside your infrastructure.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Both are perfect when you have a docs team and thousands of pages.&lt;/p&gt;

&lt;p&gt;But in my case, I just wanted three things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;My docs running next to my other services, in the same &lt;code&gt;docker-compose.yml&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Editing a &lt;code&gt;.md&lt;/code&gt; file and pushing it to GitHub as the only workflow. No build, no redeploy, no restart.&lt;/li&gt;
&lt;li&gt;The same tool for every product, with a different logo and different colors.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I could not find that, so I built it.&lt;/p&gt;


&lt;h2&gt;
  
  
  Your folder is the config
&lt;/h2&gt;

&lt;p&gt;tidydoc turns a folder of Markdown files into a clean documentation website.&lt;/p&gt;

&lt;p&gt;No build step, no database, no config file.&lt;/p&gt;

&lt;p&gt;The navigation is built from your folder tree:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docs/
├── index.md                 → /             (home page)
├── 01-getting-started/      → "Getting started" section
│   ├── 01-installation.md   → /getting-started/installation
│   └── 02-configuration.md  → /getting-started/configuration
└── 02-guides/
    ├── _meta.json           → section options
    └── webhooks/            → collapsible group
        └── index.md         → /guides/webhooks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Numeric prefixes (&lt;code&gt;01-&lt;/code&gt;) set the order. They are removed from the URL and the title.&lt;/li&gt;
&lt;li&gt;Top-level folders become &lt;strong&gt;sections&lt;/strong&gt;, deeper folders become &lt;strong&gt;collapsible groups&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The page title comes from the frontmatter, then the first &lt;code&gt;# Heading&lt;/code&gt;, then the file name.&lt;/li&gt;
&lt;li&gt;Links between pages are relative paths, so they work on GitHub too.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your brand (logo, colors, font, sidebar links) is set with environment variables. That's it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Running it takes less than a minute
&lt;/h2&gt;

&lt;p&gt;Add this to your &lt;code&gt;docker-compose.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;docs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghcr.io/tidydoc/tidydoc:latest&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8080:80"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./docs:/content:ro&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;SITE_TITLE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Acme&lt;/span&gt;
      &lt;span class="na"&gt;COLOR_PRIMARY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#6d28d9"&lt;/span&gt;
      &lt;span class="na"&gt;SIDEBAR_LINKS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Website|https://acme.com|mdi-web;GitHub|https://github.com/acme|mdi-github"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
open http://localhost:8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The volume is mounted read-only (&lt;code&gt;:ro&lt;/code&gt;). tidydoc never writes into your folder.&lt;/p&gt;

&lt;p&gt;Edit a Markdown file, refresh the page, and the change is there.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqipwi04smos2922bdgwu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqipwi04smos2922bdgwu.png" alt="TidyDoc screenshot" width="800" height="388"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  My workflow: git push, and the docs are live
&lt;/h2&gt;

&lt;p&gt;This is the part I love the most.&lt;/p&gt;

&lt;p&gt;My docs live in a GitHub repository, as plain &lt;code&gt;.md&lt;/code&gt; files. Here is the whole loop:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;I edit the Markdown with Claude Code, another AI agent, or by hand.&lt;/li&gt;
&lt;li&gt;I commit and push to GitHub.&lt;/li&gt;
&lt;li&gt;A GitHub Action syncs the &lt;code&gt;docs/&lt;/code&gt; folder to my server, into the folder mounted as &lt;code&gt;/content&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;tidydoc sees the new files and re-indexes them.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it. I never restart the container. I never rebuild an image.&lt;/p&gt;

&lt;p&gt;Here is a simplified version of the action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Sync docs&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;docs/**"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;sync&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v5&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Sync docs to the server&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;mkdir -p ~/.ssh&lt;/span&gt;
          &lt;span class="s"&gt;echo "${{ secrets.SSH_KEY }}" &amp;gt; ~/.ssh/id_ed25519&lt;/span&gt;
          &lt;span class="s"&gt;chmod 600 ~/.ssh/id_ed25519&lt;/span&gt;
          &lt;span class="s"&gt;ssh-keyscan -H "${{ secrets.SSH_HOST }}" &amp;gt;&amp;gt; ~/.ssh/known_hosts&lt;/span&gt;
          &lt;span class="s"&gt;rsync -az --delete docs/ "${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }}:/srv/acme/docs/"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why does it work without a restart?&lt;/p&gt;

&lt;p&gt;Because tidydoc watches the mounted folder. When &lt;code&gt;rsync&lt;/code&gt; writes a file, the watcher re-runs the indexing script, and the next page load gets the new content. I'll show you how in the next section.&lt;/p&gt;

&lt;p&gt;And since everything is plain Markdown in a repo, AI agents are first-class editors.&lt;/p&gt;

&lt;p&gt;I can ask Claude Code to "document the new webhook events", review the diff in a pull request, and merge. The docs are live a few seconds later.&lt;/p&gt;




&lt;h2&gt;
  
  
  No backend, no build: how it works
&lt;/h2&gt;

&lt;p&gt;The core of tidydoc is a small image: nginx and a few shell scripts.&lt;/p&gt;

&lt;p&gt;Here is everything that happens when the container starts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;container start
  ├─ entrypoint.sh  → config.js (whitelisted env vars)
  ├─ generate.sh    → content.json, /_md/&amp;lt;route&amp;gt;.md symlinks,
  │                    one HTML per page, sitemap.xml, robots.txt, llms.txt
  ├─ inotifywait    → re-runs generate.sh when /content changes
  └─ nginx          → static Vue 3 + Vuetify app, /content/* assets, raw /&amp;lt;route&amp;gt;.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;entrypoint.sh&lt;/code&gt; — Reads your environment variables and writes them into a &lt;code&gt;config.js&lt;/code&gt; file for the browser. Only whitelisted variables are copied, so nothing else from your environment leaks to the client.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;generate.sh&lt;/code&gt; — Walks the &lt;code&gt;/content&lt;/code&gt; folder and packs every &lt;code&gt;.md&lt;/code&gt; file and every &lt;code&gt;_meta.json&lt;/code&gt; into one &lt;code&gt;content.json&lt;/code&gt;. It also creates the symlinks that serve the raw Markdown of each page.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;inotifywait&lt;/code&gt; — Watches the folder and re-runs &lt;code&gt;generate.sh&lt;/code&gt; when a file changes. That's the whole "live reload", and it's what makes the git push workflow possible: the container never needs to know where the files come from. On Docker Desktop (macOS / Windows), file events don't always cross bind mounts, so you can switch to polling with &lt;code&gt;WATCH_POLL_INTERVAL&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;nginx&lt;/code&gt; — Serves a pre-built Vue 3 + Vuetify app, your assets, and the raw Markdown files.&lt;/p&gt;

&lt;h3&gt;
  
  
  Everything else happens in the browser
&lt;/h3&gt;

&lt;p&gt;The browser loads &lt;code&gt;content.json&lt;/code&gt; once. Then it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Builds the navigation from the folder tree.&lt;/li&gt;
&lt;li&gt;Renders Markdown with &lt;a href="https://github.com/markdown-it/markdown-it" rel="noopener noreferrer"&gt;markdown-it&lt;/a&gt; and highlights code with &lt;a href="https://shiki.style" rel="noopener noreferrer"&gt;Shiki&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Indexes the full-text search with &lt;a href="https://github.com/lucaong/minisearch" rel="noopener noreferrer"&gt;MiniSearch&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You have many options for search: hosted search services, or a search engine running next to your app.&lt;/p&gt;

&lt;p&gt;They are perfect when you have millions of documents.&lt;/p&gt;

&lt;p&gt;But in my case, I don't need a "real" search engine.&lt;/p&gt;

&lt;p&gt;A docs site fits in one JSON file, so the search index is built on the client and opens instantly with &lt;code&gt;⌘K&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The SEO problem with single-page apps
&lt;/h3&gt;

&lt;p&gt;This was the tricky part.&lt;/p&gt;

&lt;p&gt;A single-page app has one &lt;code&gt;index.html&lt;/code&gt;. Crawlers and link previews (Slack, X, LinkedIn) don't run JavaScript, so every page looks the same to them.&lt;/p&gt;

&lt;p&gt;So &lt;code&gt;generate.sh&lt;/code&gt; uses a &lt;code&gt;jq&lt;/code&gt; script to write one HTML file per page, with its own &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt;, description, canonical URL, Open Graph and Twitter tags.&lt;/p&gt;

&lt;p&gt;It also generates &lt;code&gt;sitemap.xml&lt;/code&gt; and &lt;code&gt;robots.txt&lt;/code&gt;, and unknown URLs return a real &lt;code&gt;404&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Still no server code. Just files that nginx serves.&lt;/p&gt;




&lt;h2&gt;
  
  
  A few things I'm proud of
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Components with plain Markdown
&lt;/h3&gt;

&lt;p&gt;No MDX, no JSX. Callouts, cards and tabs use a simple container syntax:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;::: tip Pro tip
A &lt;span class="sb"&gt;`tip`&lt;/span&gt; for best practices.
:::

::: tabs
@tab npm

npm install @acme/sdk

@tab pnpm

pnpm add @acme/sdk
:::
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Code blocks are highlighted for 200+ languages, and you can add a title after the language.&lt;/p&gt;

&lt;h3&gt;
  
  
  LLM friendly
&lt;/h3&gt;

&lt;p&gt;Every page is also available as raw Markdown: just add &lt;code&gt;.md&lt;/code&gt; to its URL.&lt;/p&gt;

&lt;p&gt;tidydoc also serves an &lt;code&gt;/llms.txt&lt;/code&gt; file that lists every page with a link to its Markdown.&lt;/p&gt;

&lt;p&gt;Each page has "Copy Markdown" and "Open" buttons, so your users can send it straight to ChatGPT or Claude.&lt;/p&gt;

&lt;h3&gt;
  
  
  One image, many products
&lt;/h3&gt;

&lt;p&gt;tidydoc is stateless. Run one container per product, each with its own folder and colors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;docs-acme&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghcr.io/tidydoc/tidydoc:latest&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;./acme-docs:/content:ro"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;SITE_TITLE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Acme&lt;/span&gt;
      &lt;span class="na"&gt;COLOR_PRIMARY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#6d28d9"&lt;/span&gt;

  &lt;span class="na"&gt;docs-globex&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghcr.io/tidydoc/tidydoc:latest&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;./globex-docs:/content:ro"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;SITE_TITLE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Globex&lt;/span&gt;
      &lt;span class="na"&gt;COLOR_PRIMARY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#059669"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No volumes on your platform? Bake the content into your own image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; ghcr.io/tidydoc/tidydoc:latest&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; docs /content&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Analytics, off by default
&lt;/h3&gt;

&lt;p&gt;Set &lt;code&gt;POSTHOG_KEY&lt;/code&gt; and tidydoc sends page views, search queries and "Copy Markdown" clicks to PostHog.&lt;/p&gt;

&lt;p&gt;Without it, the PostHog library is not even downloaded and nothing leaves the browser.&lt;/p&gt;




&lt;h2&gt;
  
  
  What it doesn't do (yet)
&lt;/h2&gt;

&lt;p&gt;I want to be honest about the trade-offs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It's a single-page app. Titles and meta tags are real HTML, but the page content is rendered with JavaScript. Google indexes it, some crawlers don't.&lt;/li&gt;
&lt;li&gt;It must be served at the root of a domain (&lt;code&gt;docs.acme.com&lt;/code&gt;), not under a sub-path (&lt;code&gt;acme.com/docs&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;There is no versioning and no i18n yet.&lt;/li&gt;
&lt;li&gt;Raw HTML in Markdown is rendered as-is, so only serve content you trust.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For HTTPS, put it behind your usual reverse proxy (Caddy, Traefik, nginx). The docs have a ready-to-copy snippet for each.&lt;/p&gt;




&lt;h2&gt;
  
  
  Help me out 🤗
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/tidydoc/tidydoc" rel="noopener noreferrer"&gt;tidydoc&lt;/a&gt; is 100% free and open-source (MIT).&lt;/p&gt;

&lt;p&gt;It's a new project, and I'm building it on my own.&lt;/p&gt;

&lt;p&gt;Versioning and i18n are next on my list. Tell me in the comments what you need, it will shape the roadmap.&lt;/p&gt;

&lt;p&gt;And if you like the idea, a star would help me more than I can describe ❤️&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/tidydoc/tidydoc" rel="noopener noreferrer"&gt;⭐ Star tidydoc on GitHub&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>opensource</category>
      <category>docker</category>
      <category>documentation</category>
    </item>
  </channel>
</rss>
