Skip to content

Commit 28a963b

Browse files
committed
blog: add how-we-do-product-documentation-engineering
1 parent 288399e commit 28a963b

File tree

4 files changed

+325
-0
lines changed

4 files changed

+325
-0
lines changed
Loading
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,325 @@
1+
---
2+
title: "How We Do Documentation Engineering"
3+
meta_title: "How We Do Documentation Engineering"
4+
description: ""
5+
date: 2024-12-16T08:00:00Z
6+
image: "/images/posts/2024/documentation-engineering/how-we-do-product-documentation-engineering.jpg"
7+
categories: ["Product", "Documentation", "Engineering"]
8+
author: "Medcl"
9+
tags: ["Product", "Documentation", "Engineering"]
10+
draft: false
11+
---
12+
13+
At INFINI Labs, we see product documentation as an integral part of the product development process. Effective documentation ensures that users understand, adopt, and get the most out of our offerings.
14+
15+
Take a look at our documentation site: [https://docs.infinilabs.com/](https://docs.infinilabs.com/). It hosts detailed documentation for each of our products.
16+
17+
Managing comprehensive documentation might seem like an enormous task, especially since we don’t have a dedicated documentation team. However, with limited resources and many competing priorities, we’ve streamlined a practical and efficient approach to product documentation engineering.
18+
19+
So, how do we do it?
20+
21+
---
22+
23+
## The Tools We Use
24+
25+
Our documentation workflow relies on the following tools:
26+
27+
- **[GitHub Pages](https://pages.github.com/)**: For hosting our documentation website.
28+
- **[GitHub Actions](https://github.com/features/actions)**: To automate builds and deployments.
29+
- **[Hugo](https://gohugo.io/)**: A fast and flexible static site generator.
30+
- **[Markdown](https://www.markdownguide.org/)**: To write clean and easily maintainable documentation.
31+
32+
We love GitHub's ecosystem for its reliability and developer-friendly features. Using GitHub Pages, we can host our documentation effortlessly. Once our documentation is compiled into static files using Hugo, it’s lightweight, fast, and easy to serve to users.
33+
34+
To enhance the user experience further, we integrate **offline search functionality** powered by our own [pizza-searchbox](https://github.com/infinilabs/pizza-searchbox/). This ensures users can quickly find what they need, even without an internet connection.
35+
36+
---
37+
38+
## How It All Works Together
39+
40+
### Organizing Components Across Repositories
41+
42+
We maintain separate repositories for each part of the documentation workflow. The final compiled version of all product documentation lives here:
43+
44+
- Compiled docs: [https://github.com/infinilabs/docs](https://github.com/infinilabs/docs)
45+
- Hugo theme for docs: [https://github.com/infinilabs/docs-theme](https://github.com/infinilabs/docs-theme)
46+
- Product with docs: [https://github.com/infinilabs/gateway](https://github.com/infinilabs/gateway)
47+
48+
By separating concerns, we make it easier to manage updates, streamline collaboration, and keep everything organized.
49+
50+
51+
As you can see the layout of compiled folder is looks like this:
52+
53+
![Documentation Engineering](/images/posts/2024/documentation-engineering/compiled-documents-folder-layout.png)
54+
55+
Each products have folder for each different version, and the `main` is alwasy point to the latest version.
56+
57+
### And how did these static docs coming from?
58+
59+
Checkout this specify [product's](https://github.com/infinilabs/gateway/tree/main/docs) repo for example:
60+
61+
![Documentation Engineering](/images/posts/2024/documentation-engineering/product-docs-layout.jpg)
62+
63+
As you can see, in the product’s repository, there’s a folder named `docs`, which contains all the documentation specific to that product.
64+
65+
Alongside it, there’s a `config.yaml` defines the basic configuration for the product:
66+
67+
```yaml
68+
# VERSIONS=latest,v1.0 hugo --minify --baseURL="/product/v1.0/" -d public/product/v1.0
69+
70+
title: INFINI Gateway
71+
theme: book
72+
73+
# Book configuration
74+
disablePathToLower: true
75+
enableGitInfo: false
76+
77+
# Needed for mermaid/katex shortcodes
78+
markup:
79+
goldmark:
80+
renderer:
81+
unsafe: true
82+
tableOfContents:
83+
startLevel: 1
84+
85+
# Multi-lingual mode config
86+
# There are different options to translate files
87+
# See https://gohugo.io/content-management/multilingual/#translation-by-filename
88+
# And https://gohugo.io/content-management/multilingual/#translation-by-content-directory
89+
defaultContentLanguage: en
90+
languages:
91+
en:
92+
languageName: English
93+
contentDir: content.en
94+
weight: 3
95+
96+
97+
menu:
98+
before: []
99+
after:
100+
- name: "Github"
101+
url: "https://github.com/infinilabs/gateway"
102+
weight: 10
103+
104+
...
105+
EMITTED
106+
...
107+
```
108+
109+
Make sure you changed the right github's repo address and the product name.
110+
111+
And also there’s a `Makefile` that defines how we build the docs:
112+
113+
```shell
114+
SHELL=/bin/bash
115+
116+
# Basic info
117+
PRODUCT?= $(shell basename "$(shell cd .. && pwd)")
118+
BRANCH?= main
119+
VERSION?= $(shell [[ "$(BRANCH)" == "main" ]] && echo "main" || echo "$(BRANCH)")
120+
CURRENT_VERSION?= $(VERSION)
121+
VERSIONS?= "main"
122+
OUTPUT?= "/tmp/docs"
123+
THEME_FOLDER?= "themes/book"
124+
THEME_REPO?= "https://github.com/infinilabs/docs-theme.git"
125+
THEME_BRANCH?= "main"
126+
127+
.PHONY: docs-build
128+
129+
default: docs-build
130+
131+
docs-init:
132+
@if [ ! -d $(THEME_FOLDER) ]; then echo "theme does not exist";(git clone -b $(THEME_BRANCH) $(THEME_REPO) $(THEME_FOLDER) ) fi
133+
134+
docs-env:
135+
@echo "Debugging Variables:"
136+
@echo "PRODUCT: $(PRODUCT)"
137+
@echo "BRANCH: $(BRANCH)"
138+
@echo "VERSION: $(VERSION)"
139+
@echo "CURRENT_VERSION: $(CURRENT_VERSION)"
140+
@echo "VERSIONS: $(VERSIONS)"
141+
@echo "OUTPUT: $(OUTPUT)"
142+
143+
docs-config: docs-init
144+
cp config.yaml config.bak
145+
# Detect OS and apply the appropriate sed command
146+
@if [ "$$(uname)" = "Darwin" ]; then \
147+
echo "Running on macOS"; \
148+
sed -i '' "s/BRANCH/$(VERSION)/g" config.yaml; \
149+
else \
150+
echo "Running on Linux"; \
151+
sed -i 's/BRANCH/$(VERSION)/g' config.yaml; \
152+
fi
153+
154+
docs-build: docs-config
155+
hugo --minify --theme book --destination="$(OUTPUT)/$(PRODUCT)/$(VERSION)" \
156+
--baseURL="/$(PRODUCT)/$(VERSION)"
157+
@$(MAKE) docs-restore-generated-file
158+
159+
docs-place-redirect:
160+
echo "<!DOCTYPE html> <html> <head> <meta http-equiv=refresh content=0;url=main /> </head> <body> <p><a href=main />REDIRECT TO THE LATEST_VERSION</a>.</p> </body> </html>" > $(OUTPUT)/$(PRODUCT)/index.html
161+
162+
docs-restore-generated-file:
163+
mv config.bak config.yaml
164+
165+
```
166+
167+
168+
Usually, there’s no need to make any changes—simply copy these files to your new product, and everything will work seamlessly.
169+
170+
---
171+
172+
## Automation Makes Everything Easier
173+
174+
And we use `.github/workflows/build-docs.yml` to define how to build the documentation once someone pushed the code, or someone released a new version, take a look at the github actions:
175+
176+
```yaml
177+
name: Build and Deploy Docs
178+
179+
on:
180+
push:
181+
branches:
182+
- main
183+
- 'v*'
184+
tags:
185+
- 'v*'
186+
187+
jobs:
188+
build-deploy-docs:
189+
runs-on: ubuntu-latest
190+
191+
steps:
192+
- name: Checkout Product Repo
193+
uses: actions/checkout@v2
194+
with:
195+
fetch-depth: 0
196+
197+
- name: Set Variables Based on Ref
198+
id: vars
199+
run: |
200+
PRODUCT_NAME=$(basename $(pwd)) # Get the directory name as the product name
201+
echo "PRODUCT_NAME=$PRODUCT_NAME" >> $GITHUB_ENV
202+
CURRENT_REF=${GITHUB_REF##*/}
203+
IS_SEMVER=false
204+
SEMVER_REGEX="^v([0-9]+)\.([0-9]+)\.([0-9]+)$"
205+
206+
if [[ "${GITHUB_REF_TYPE}" == "branch" ]]; then
207+
if [[ "$CURRENT_REF" == "main" ]]; then
208+
echo "VERSION=main" >> $GITHUB_ENV
209+
echo "BRANCH=main" >> $GITHUB_ENV
210+
elif [[ "$CURRENT_REF" =~ $SEMVER_REGEX ]]; then
211+
IS_SEMVER=true
212+
echo "VERSION=$CURRENT_REF" >> $GITHUB_ENV
213+
echo "BRANCH=$CURRENT_REF" >> $GITHUB_ENV
214+
else
215+
echo "Branch '$CURRENT_REF' is not a valid semantic version. Skipping build."
216+
exit 0
217+
fi
218+
elif [[ "${GITHUB_REF_TYPE}" == "tag" ]]; then
219+
if [[ "$CURRENT_REF" =~ $SEMVER_REGEX ]]; then
220+
IS_SEMVER=true
221+
echo "VERSION=$CURRENT_REF" >> $GITHUB_ENV
222+
echo "BRANCH=main" >> $GITHUB_ENV # Set BRANCH to 'main' for tags
223+
else
224+
echo "Tag '$CURRENT_REF' is not a valid semantic version. Skipping build."
225+
exit 0
226+
fi
227+
fi
228+
229+
# Gather branches and tags, filter for semantic versions, sort, remove duplicates
230+
VERSIONS=$(git for-each-ref refs/remotes/origin refs/tags --format="%(refname:short)" | \
231+
grep -E "^v[0-9]+\.[0-9]+\.[0-9]+$" | sort -Vr | uniq | tr '\n' ',' | sed 's/,$//')
232+
echo "VERSIONS=main,$VERSIONS" >> $GITHUB_ENV
233+
234+
- name: Install Hugo
235+
run: |
236+
wget https://github.com/gohugoio/hugo/releases/download/v0.79.1/hugo_extended_0.79.1_Linux-64bit.tar.gz
237+
tar -xzvf hugo_extended_0.79.1_Linux-64bit.tar.gz
238+
sudo mv hugo /usr/local/bin/
239+
240+
- name: Checkout Docs Repo
241+
uses: actions/checkout@v2
242+
with:
243+
repository: infinilabs/docs
244+
path: docs-output
245+
token: ${{ secrets.DOCS_DEPLOYMENT_TOKEN }}
246+
247+
- name: Build Documentation
248+
run: |
249+
(cd docs && OUTPUT=$(pwd)/../docs-output make docs-build docs-place-redirect)
250+
251+
- name: Commit and Push Changes to Docs Repo
252+
working-directory: docs-output
253+
run: |
254+
git config user.name "GitHub Actions"
255+
git config user.email "[email protected]"
256+
257+
if [[ -n $(git status --porcelain) ]]; then
258+
git add .
259+
git commit -m "Rebuild $PRODUCT_NAME docs for version $VERSION"
260+
git push origin main
261+
else
262+
echo "No changes to commit."
263+
fi
264+
265+
- name: Rebuild Docs for Latest Version (main), if not already on main
266+
run: |
267+
# Only rebuild the main branch docs if the current ref is not "main"
268+
if [[ "$CURRENT_REF" != "main" ]]; then
269+
echo "Switching to main branch and rebuilding docs for 'latest'"
270+
271+
# Checkout the main branch of the product repo to rebuild docs for "latest"
272+
git checkout main
273+
274+
# Ensure the latest changes are pulled
275+
git pull origin main
276+
277+
# Build Docs for Main Branch (latest)
278+
(cd docs && OUTPUT=$(pwd)/../docs-output VERSION="main" BRANCH="main" make docs-build docs-place-redirect)
279+
280+
# Commit and Push Latest Docs to Main
281+
cd docs-output
282+
git config user.name "GitHub Actions"
283+
git config user.email "[email protected]"
284+
285+
if [[ -n $(git status --porcelain) ]]; then
286+
git add .
287+
git commit -m "Rebuild $PRODUCT_NAME docs for main branch with latest version"
288+
git push origin main
289+
else
290+
echo "No changes to commit for main."
291+
fi
292+
else
293+
echo "Current ref is 'main', skipping rebuild for 'latest'."
294+
fi
295+
working-directory: ./ # Working in the product repo
296+
```
297+
298+
If you look closely at the GitHub Actions workflow, it simplifies the entire process by automating key tasks:
299+
- Monitor Branches and Tags: Watches for branches or tags starting with v and validates them as semantic versioned branches or tags. Only valid versions trigger the documentation build process.
300+
- Fetch Theme: Pulls the documentation theme from a separate repository to ensure a consistent look and feel across all products.
301+
- Build and Deploy: Compiles the documentation into static files using Hugo, commits the changes with a clear and informative message, and pushes the updates to the docs repository.
302+
303+
Through GitHub Actions, the entire workflow becomes seamless:
304+
- Compile: Converts Markdown files into a polished static site using Hugo.
305+
- Deploy: Publishes the site effortlessly to GitHub Pages.
306+
307+
This automation reduces manual effort, ensures consistency across documentation, and allows us to focus on delivering quality content to users.
308+
309+
---
310+
311+
## Why This Approach Works for Us
312+
313+
1. **Efficiency**: With automation, we can focus on content rather than operations.
314+
2. **Scalability**: As our product offerings grow, this workflow scales effortlessly.
315+
3. **User Experience**: A fast, searchable, and offline-ready documentation site means better support for our users.
316+
317+
---
318+
319+
## Final Thoughts
320+
321+
Product documentation isn’t just a necessity—it’s a competitive advantage. By leveraging the right tools and automating where possible, we’ve built a process that delivers high-quality documentation without needing a dedicated team.
322+
323+
Want to see it in action? Visit our documentation site here: [https://docs.infinilabs.com/](https://docs.infinilabs.com/).
324+
325+
Let us know what you think! Your feedback helps us improve not only our products but also the way we document and share them with the world.

0 commit comments

Comments
 (0)